The same code behaves diffrent on ESP32S2 and ATtiny202

Hello,
I am writing a simple timer program and for testing it should turn an led on when an alert occurs.
The code works without problem on my ESP32S2. (LED 5 sec on and then 5 sec off)

On the Attiny202 it does not work for some reason.

The initial test lighting did work (LED is for 2 sec on) on both controllers. Can there be an overflow or different sizes of variables? Or is the memory not enought?

I do not know what I could test next or try to fix.

This is the code:
Main.cpp

#include <Arduino.h>
#include "SimpleTimer.h"
// #define DEBUGGING

SimpleTimer timer;

// Pin to switch the device on and off
int8_t outputPin = 2;
int8_t currenState = LOW;

int8_t getNextState()
{
    currenState = currenState == HIGH ? LOW : HIGH;
    return currenState;
}

void testAddAlerts()
{
    // timer.addAlert(timer.convertTimeToMilliSec(0, 0, 5), B01111111);
    timer.addAlert(00, 00, 05, B01111111);
    timer.setAllAlertsActive(true);
}

void setup()
{

#ifdef DEBUGGING
    Serial.begin(115200);
#endif

    pinMode(outputPin, OUTPUT);
    testAddAlerts();
    timer.setTimer(0, 0, 0);
    digitalWrite(outputPin, getNextState());
    unsigned long future = millis() + 2000;
    while (future > millis())
    {
        delay(100);
    }
    digitalWrite(outputPin, getNextState());
}

void loop()
{

    if (timer.update() == 1)
    {
        // Serial.println("Alert");

        digitalWrite(outputPin, getNextState());
        // Resets the timer so there is an output every alert
        timer.setTimer(0,0,0);
        timer.setAllAlertsActive(true);
    }
}

SimpleTimer.h

#include <Arduino.h>
#pragma once
#define CHECKONLYEVERYSECOND
// #define DEBUGGING

struct Alert
{
    Alert(uint32_t time,  uint8_t executionDays)
    {
        executionTime = time;
        setExecutionDays(executionDays);
        activate(true);
    }
    /**
     * @brief Set the execution time
     *
     * @param time
     */
    void setExecutionTime(uint32_t time)
    {
        executionTime = time;
    }

    uint8_t getExecutionDays()
    {
        return data >= 128 ? data - 128 : data;
    }

    void setExecutionDays(uint8_t bitmask)
    {
        boolean active = isActive();
        data = bitmask;
        activate(active);
    }
    /**
     * @brief Activates or deactivates the alert
     *
     * @param activate
     */
    void activate(bool activate)
    {
        if (isActive())
        {
            if (!activate)
            {
                data -= 128;
            }
        }
        else
        {
            if (activate)
            {
                data += 128;
            }
        }
    }
    /**
     * @brief Returns if the alert is currently active or not
     *
     * @return true The alert is active
     * @return false The alert is deactivated
     */
    bool isActive()
    {
        if (data >= 128)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    Alert *nextAlert = nullptr;
    /**
     * @brief data saves multiple differnt data in an 8 bit integer
     * The first bit indcates if the alert is active
     *
     *
     */
    uint8_t data;
    uint32_t executionTime = 0;
};

/**
 * @brief Keeps track of time and triggers when an alert occures
 * (Use the update function for that)
 *
 */
class SimpleTimer
{

public:
    SimpleTimer();
    ~SimpleTimer();
    /**
     * @brief Updates the time with the elapsed time and checks if an alert is triggered. Also it resets the time and the alerts when "a day" is passed.
     *
     * @return returns 1 if an alert was triggered and 0 if noting happend
     */
    int update();

    /**
     * @brief Converts the time given as normal time like 18:30:30 in milliseconds
     *
     * @param hours A value between 0-23
     * @param minutes A value between 0-59
     * @param seconds A value between 0-59
     * @return The time in milliseconds
     */
    uint32_t convertTimeToMilliSec( uint8_t hours,  uint8_t minutes,  uint8_t seconds);

    /**
     * @brief Adds an alert to the timer
     *
     * @param time The time when the alert should trigger
     */
    void addAlert(uint32_t time,  uint8_t weekdays);

    /**
     * @brief Adds an alert to the timer
     *
     * @param hours A value between 0-23
     * @param minutes A value between 0-59
     * @param seconds A value between 0-59
     */
    void addAlert( uint8_t hours,  uint8_t minutes,  uint8_t seconds,  uint8_t weekdays);

    /**
     * @brief Remove the alert on the given index
     *
     * @param index the position of the alert
     * @return boolean which indicates if the removing was succesfull
     */
    boolean removeAlert(int index);

    /**
     * @brief Remove the alert with the time
     *
     * @param time the time which should get removed
     * @return boolean which indicates if the removing was succesfull
     */
    boolean removeAlert(uint32_t time);

    /**
     * @brief Get the alert with the given index
     *
     * @param index An positive integer which is the desired index of the alert
     * @return Returns an Alert if it exists. If there is no Alert on this index it returns nullptr.
     */
    Alert *getAlert( uint8_t index);

    /**
     * @brief Sets all alerts inactive or active ´
     *
     * @param active Value which defines if all alerts should be active or inactive
     */
    void setAllAlertsActive(bool active);

    /**
     * @brief Sets the current time
     *
     * @param hours A value between 0-23
     * @param minutes A value between 0-59
     * @param seconds A value between 0-59
     */
    void setTimer( uint8_t hours,  uint8_t minutes,  uint8_t seconds);

    // Used for debugging
    void printTime(uint32_t time)
    {
#ifdef DEBUGGING
        // Uses too mutch resources so it will only used to debug
        int hours = time / (1000 * 60 * 60);
        time = time - hours * (1000 * 60 * 60);
        int minutes = time / (1000 * 60);
        time = time - minutes * (1000 * 60);
        int seconds = time / 1000;
        time = time - seconds * 1000;
        Serial.print(hours);
        Serial.print(":");
        Serial.print(minutes);
        Serial.print(":");
        Serial.print(seconds);
        Serial.print(":");
        Serial.println(time);
#endif
    }

protected:
    uint32_t lastTimeUpdate = 0;
    /**
     * @brief Represents the current daytime in milliseconds if it synced before, else it is just a virtual day with 24h
     *
     */
    uint32_t time = 0;
    /**
     * @brief Saves all alerts
     *
     */
    Alert *rootAlert = nullptr;
    // Saves the current time just here so the microncontroller does not need to reserve always new storage
    uint32_t currentTime;

#ifdef CHECKONLYEVERYSECOND
    /**
     * @brief Stores the milliseconds elapesed since the last full second was reached
     * Only use when CHECKONLYEVERYSECOND is defined
     *
     */
    int16_t lastSecUpdate;
#endif
};

SimpleTimer.cpp

#include "SimpleTimer.h"

SimpleTimer::SimpleTimer()
{
    time = 0;
}

SimpleTimer::~SimpleTimer()
{
}

uint32_t SimpleTimer::convertTimeToMilliSec(uint8_t hours, uint8_t minutes, uint8_t seconds)
{
    return ((((hours * 60) + minutes) * 60UL) + seconds) * 1000UL;
}

void SimpleTimer::setTimer(uint8_t hours, uint8_t minutes, uint8_t seconds)
{
    // TODO check if params are valid not negativ or to high
    // Calculates the time in milli seconds
    time = convertTimeToMilliSec(hours, minutes, seconds);
}

void SimpleTimer::setAllAlertsActive(bool activ)
{
    Alert *currentAlert = rootAlert;
    while (currentAlert != nullptr)
    {
        currentAlert->activate(activ);
        currentAlert = currentAlert->nextAlert;
    }
}

void SimpleTimer::addAlert(uint8_t hours, uint8_t minutes, uint8_t seconds, uint8_t weekdays)
{
    addAlert(convertTimeToMilliSec(hours, minutes, seconds), weekdays);
}

void SimpleTimer::addAlert(uint32_t time, uint8_t weekdays)
{
    if (rootAlert != nullptr)
    {
        Alert *currentAlert = rootAlert;
        while (currentAlert->nextAlert != nullptr)
        {
            currentAlert = currentAlert->nextAlert;
        }

        currentAlert->nextAlert = new Alert(time, weekdays);
        currentAlert->nextAlert->activate(true);
    }
    else
    {
        rootAlert = new Alert(time, weekdays);
    }
}

int SimpleTimer::update()
{
    uint8_t returnValue = 0;
    currentTime = millis();
    time += currentTime - lastTimeUpdate;

#ifdef CHECKONLYEVERYSECOND
    lastSecUpdate += currentTime - lastTimeUpdate;
    if (lastSecUpdate >= 1000)
    {
        printTime(time);
        lastSecUpdate = lastSecUpdate - 1000;
#endif
        if (rootAlert != nullptr)
        {
            Alert *currentAlert = rootAlert;
            while (currentAlert != nullptr)
            {
                if (currentAlert->isActive() && currentAlert->executionTime <= time)
                {
                    currentAlert->activate(false);
                    returnValue = 1;
                }
                else
                {
                    currentAlert = currentAlert->nextAlert;
                }
            }
        }
        // Test if "a day" has passed
        if (time >= 86400000L)
        {
            time = time - 86400000L;
            setAllAlertsActive(true);
        }
#ifdef CHECKONLYEVERYSECOND
    }
#endif
    lastTimeUpdate = currentTime;
    return returnValue;
}

Alert *SimpleTimer::getAlert(uint8_t index)
{
    if (index < 0)
    {
        return nullptr;
    }
    Alert *askedAlert = rootAlert;
    for (int8_t i = 0; i <= index; i++)
    {

        if (index == i)
        {
            return askedAlert;
        }
        if (askedAlert->nextAlert != nullptr)
        {
            askedAlert = askedAlert->nextAlert;
        }
        else
        {
            return nullptr;
        }
    }
    return askedAlert;
}

I don't know either controller but suspect a SimpleTimer library problem. Does it really support the ATtiny202?

I wrote the SimpleTimer myself so it should not have a problem with that (no external lib used). It depends on the millis() function but that works too.
It executes a the first part in the setup but in the loop it does not switch the LED on. So my guess was that there maybe is a problem with the ram or something alike. Did I maybe use a dummy function which is not implemented for the attiny so far?

There could be a problem with the code which I don´t see ,but it works on one controller but not the other one.

Does the code run on your micro controller or do you have the same problem like I have?

  • ATtiny 202/402
  • Maybe try another pin ... digital 2 is used by serial (TXD1).
  • Try PA3 (digital 4)?

That was a good hint but it seems that was not the problem. (I do not use Serial at the attiny since it consumes too mutch flash memory).
The first two outputs in the setup are working, thats why i am thinking that there maybe an problem in the update function.

Using one of these?

LED is on PA7

image

... or just check the signal on all pins.

... or try a low level method of working with the pins. Here, I've used these pins:

PIN1_bm
PIN2_bm
PIN3_bm
PIN6_bm
PIN7_bm

OK, if the led pin is working, then it could be the Arduino function millis() isn't available

Could try using

#include <util/delay.h>

for your timing functions. <--- blocking

(or use the system clock) <---non-blocking

I am currently try to sort of debug it and it might have trouble to allocate the storage for the alerts. Which is alittle bit odd.
When I add an

if(rootAlert == nullptr)
        {return 1;}

to the update method then it does blink. But that means the there is no alert in the memory.
So maybe there is an problem with the addAlert(uint32_t time, uint8_t weekdays) function (That would be that function for test purposes)

int SimpleTimer::update()
{
    uint8_t returnValue = 0;
    currentTime = millis();
    time += currentTime - lastTimeUpdate;

#ifdef CHECKONLYEVERYSECOND
    lastSecUpdate += currentTime - lastTimeUpdate;
    if (lastSecUpdate >= 1000)
    {
        printTime(time);
        lastSecUpdate = lastSecUpdate - 1000;
#endif
        if(rootAlert == nullptr)
        {return 1;}
        else if (rootAlert != nullptr)
        {
            Alert *currentAlert = rootAlert;
            while (currentAlert != nullptr)
            {
                if (currentAlert->isActive() && currentAlert->executionTime <= time)
                {
                    currentAlert->activate(false);
                    returnValue = 1;
                }
                else
                {
                    currentAlert = currentAlert->nextAlert;
                }
            }
        }
        // Test if "a day" has passed
        if (time >= 86400000L)
        {
            time = time - 86400000L;
            setAllAlertsActive(true);
        }
#ifdef CHECKONLYEVERYSECOND
    }
#endif
    lastTimeUpdate = currentTime;
    return returnValue;
}

I tryed different pins so i guess they are fine. (I didn´t test the programming pin as output)

This code runs fine so i guess the millis() and delay function do work too.

pinMode(outputPin, OUTPUT);
    testAddAlerts();
    timer.setTimer(0, 0, 0);
    digitalWrite(outputPin, getNextState());
    unsigned long future = millis() + 2000;
    while (future > millis())
    {
        delay(100);
    }
    digitalWrite(outputPin, getNextState());

Thats currently all informations which I got.

I just tried that:

Alert* testAlert = new Alert(timer.convertTimeToMilliSec(0,0,1),B11111111);
void testAddAlerts()
{
    timer.rootAlert = testAlert;
    // if there is no entry 
    if(timer.rootAlert == nullptr)
    {
        digitalWrite(outputPin, HIGH);
    }
}

And when I call this function it is still switching the LED on. But the value should not be a nullpointer since timer.rootAlert gets an value. How is that possible?

Can compiler for the attiny not handle pointers?

If you're using this core , perhaps someone there would have the answer (also check issues and discussions)

I investigated alittle more and I found out that, there is a problem with saving the data of a struct or class in a pointer.

I striped down the code to the problem and now I got this:

#include <Arduino.h>
struct Alert
{
  Alert(){}
  Alert *nextAlert = nullptr;
};

Alert *rootAlert;

int led = 4;

void setup()
{
  // initialize digital pin as an output.
  pinMode(led, OUTPUT);
  rootAlert = new Alert();
}

void loop()
{
  if (rootAlert != nullptr)
  {
    digitalWrite(led, HIGH);
  }
  else
  {
    digitalWrite(led, HIGH);
    delay(1000);
    digitalWrite(led, LOW);
  }

  delay(1000); // wait for a second
}

If it stored a value the led is just on but if there is still a nullpointer it is blinking.

So I wonder what can I do now?

You are right maybe I can find there something. I just wish, that I had one of the bigger chips too so i could test if it is a resource problem or something like that.

I've worked with the 4809 40-pin IC on a breadboard before ... worked great!

Don't overlook the awesome MegaCoreX core which also works with the Arduino Nano Every.

That way you can compare a more capable IC with similar registers by using a different core and different hardware at the same time.

You created that object right in setup().

Yes I created the object in the setup and initialised the rootAlert pointer with it. (The rootAlert is declared as global variable outside of the setup and loop function).
And in the setup it checks if it is a nullpointer or if it stored the address of the object.

Not in the code you have presented :frowning:
Did you mean rootAlert.nextAlert instead?

Thats a typo there should be loop instead of setup.

When I would check for rootAlert->nextAlert, the program would crash since rootAlert is a nullpointer (when I use the attiny202). The problem is that the code only works on my ESP32S2 but not on my ATtiny since it does not store the address of the created object or it does not reserve storage which results in a nullpointer for rootAlert.

This line will work fine on ESP, but can cause overflow on the 8bit controller

change it to
return ((((hours * 60) + minutes) * 60UL) + seconds) * 1000UL;

Check your code for other similar issues.

Ok thank you I will do that, can you explain what the UL does so I know when I should use it?

But in the minimal example (The same code behaves diffrent on ESP32S2 and ATtiny202 - #10 by djpx)
I do not use that line do you may have any other hints?

So the U just means unsigned noting more?

Does someone here maybe got another microcontroller which also uses the megatinycore and has the same problem or could my chip or the chip series cause the problem?

  • Tested OK on a Nano Every (used pin 13 for LED)
  • Selected Registers emulation: "none (ATMEGA4809)"
  • Made some changes
  • Used an empty dummy _test.ino to allow uploading your program using Arduino 1.8.19

Here's the only warning:

In file included from C:\Users\dlloy\Documents\_test\Main.cpp:2:0:
C:\Users\dlloy\Documents\_test\SimpleTimer.h: In member function 'void SimpleTimer::printTime(uint32_t)':
C:\Users\dlloy\Documents\_test\SimpleTimer.h:170:29: warning: unused parameter 'time' [-Wunused-parameter]
     void printTime(uint32_t time)
                             ^~~~

Here's a zip of the _test folder files.

_test.zip (3.2 KB)

If Registers emulation: "ATMEGA328" is used, this extra warning occurs, but the LED on pin 13 flashes OK (on 5 sec, off 5 sec).

C:\Users\dlloy\Documents\_test\SimpleTimer.cpp: In member function 'Alert* SimpleTimer::getAlert(uint8_t)':
C:\Users\dlloy\Documents\_test\SimpleTimer.cpp:102:15: warning: comparison is always false due to limited range of data type [-Wtype-limits]
     if (index < 0)
         ~~~~~~^~~