0

I use Neopixels (64 LEDs), and I have a function called level_up that gets different led_num each time. Generally, it is a level bar; level[1] will light all the LEDs in a sequence from 0 to 28, level[2] all the LEDs from 29 to 48, etc. The function I attached works fine, but I need to change the delay to millis() and not sure how. Any thoughts?

uint8_t level[] = {0, 28, 48, 60, 64};  //levels 0 to 4


void level_up(uint8_t wait, uint8_t led_num) {
    uint8_t start_point;
    if (led_num == level[1]) start_point = 0;   //up from level 0 to 1
    if (led_num == level[2]) start_point = 28;  //up from level 1 to 2
    if (led_num == level[3]) start_point = 48;  //up from level 2 to 3
    if (led_num == level[4]) start_point = 60;  //...

    for (uint8_t i = start_point; i < led_num; i++) {
        strip.setPixelColor(i, strip.Color(0, 0, 255));
        strip.show();
        delay(wait);  //TODO: change it to timer
    }
}

void loop() {
    if (plus_btn.pressed()) {
        score++;
        if (score >= 4) {
            score = 4;
        }
    }
    if (minus_btn.pressed()) {
        score--;
        if (score <= 0) {
            score = 0;
        }
    }

switch (score) {
    case 0:
        if (last_score == 1) level_down(50, level[0]);
        last_score = 0;
        break;
    case 1:
        // if last_score was 0 make the blue effect because level is up
        if (last_score == 0) level_up(50, level[1]);
        // if last_score was 2 make the red effect because level is down
        if (last_score == 2) level_down(50, level[1]);
        last_score = 1;
        break;
    case 2:
        if (last_score == 1) level_up(50, level[2]);
        if (last_score == 3) level_down(50, level[2]);
        last_score = 2;
        break;
    case 3:
        if (last_score == 2) level_up(50, level[3]);
        if (last_score == 4) level_down(50, level[3]);
        last_score = 3;
        break;
    case 4:
        winning_timer.start();
        winning();
        digitalWrite(WINNING_SENSOR_PIN, HIGH);
        break;
}

Serial.println(score);

}

  • 1
    The parameter in `delay()` is in milliseconds. – seccpur Nov 04 '19 at 06:25
  • ... and `delay()`s parameter is an `unsigned long`. Your `wait` variable only allows for `255 ms` long delays. – Ted Lyngmo Nov 04 '19 at 06:31
  • I know; I don't need more than that :) My question was about changing this function to work with millis(). – KD Technology Nov 04 '19 at 06:36
  • `millis()` just reports milliseconds since you started. What do you want to use it for? – Ted Lyngmo Nov 04 '19 at 06:40
  • I need this function to work in multitasking with other actions the Arduino executes. The delay stops the processor while millis() or timers aren't. – KD Technology Nov 04 '19 at 06:47
  • then implement your own delay function and instead of an empty loop, execute whatever has to be done in between. You need to implement an own pre-emptive multitaskting on arduino, because the hardware does not help you with multi-tasking. – MaxC Nov 04 '19 at 06:57
  • You could try to use a timer, but it is considered bad style to have a loop in an interrupt routine (for good reasons). If you still want to do that, it is simply archived by adding a timer function with a well defined name and configure your processor to trigger it. Its all in the data sheet. – MaxC Nov 04 '19 at 07:00
  • @KDTechnology you could take a look at implemented timers for Arduino. Take the simplest https://github.com/schinken/SimpleTimer/, just to understand the idea. Later you could choose any other with better performance. – Aleksej Vasinov Nov 04 '19 at 08:10

3 Answers3

1

Using millis() will not block the for-loop like delay(). Therefore I think you will have to adapt the code that is calling your method, because at the moment it looks like your code depends on being blocked in the for-loop. But generally you would use millis() like in the code sample below. You store the start-timestamp and then do something after your wait-period is over.

uint8_t level[] = {0, 28, 48, 60, 64};  //levels 0 to 4

uint8_t counter;
uint8_t end_point;
bool show_level;

void level_up(uint8_t wait, uint8_t led_num) {
    if (led_num == level[1]) counter = 0;   //up from level 0 to 1
    if (led_num == level[2]) counter = 28;  //up from level 1 to 2
    if (led_num == level[3]) counter = 48;  //up from level 2 to 3
    if (led_num == level[4]) counter = 60;  //...
    show_level =true;
    end_point = led_num;
}

bool set_pixel_color(uint8_t wait) 
{
    if(timestamp - millis() == wait)
    {
        strip.setPixelColor(counter, strip.Color(0, 0, 255));
        strip.show();
        timestamp = millis();
        return true; // incremented 
    }    
    return false;
}

void show_level_led_strip()
{
    if(show_level)
    {
        if(counter > end_point) // escape when the counter gets bigger then the current led_num
        {
            show_level = false;
        }
        else
        {
            if(set_pixel_color(50))
            {
                counter++;
            }
        }
    }
}

void loop() {
    if (plus_btn.pressed()) {
        score++;
        if (score >= 4) {
            score = 4;
        }
    }
    if (minus_btn.pressed()) {
        score--;
        if (score <= 0) {
            score = 0;
        }
    }

    switch (score) {
        case 0:
            if (last_score == 1) level_down(level[0]);
            last_score = 0;
            break;
        case 1:
            // if last_score was 0 make the blue effect because level is up
            if (last_score == 0) level_up(level[1]);
            // if last_score was 2 make the red effect because level is down
            if (last_score == 2) level_down(level[1]);
            last_score = 1;
            break;
        case 2:
            if (last_score == 1) level_up(level[2]);
            if (last_score == 3) level_down(level[2]);
            last_score = 2;
            break;
        case 3:
            if (last_score == 2) level_up(level[3]);
            if (last_score == 4) level_down(level[3]);
            last_score = 3;
            break;
        case 4:
            winning_timer.start();
            winning();
            digitalWrite(WINNING_SENSOR_PIN, HIGH);
            break;
    }

    show_level_led_strip();
}

Serial.println(score);
andy meissner
  • 1,202
  • 5
  • 15
0

This doesn't answer your question directly, but the strategy that I use gives me any number of timed events without my program blocking in millis().

Set a deadline in the future and enclose the delayed action in an if statement that polls millis() until that deadline is reached. It's not perfect because the software timing loses time due to processing, and because of the millis() overflow and wrap-around issue (look it up on arduino.cc).

/* Global variables (constexpr creates a compile time only constant) */
constexpr uint32_t WAIT_INTERVAL = 10;  // interval is 10ms
uint32_t deadline = 0;  // when to run next

// inside loop()
    uint32_t now = millis();  // capture the current millis() value
    if(now >= deadline)
    {
        deadline = now + WAIT_INTERVAL;  // push the next deadline into the future
        // perform timed periodic operations here (call function or whatever)
    }
Louis B.
  • 487
  • 1
  • 4
  • 8
0

After reading your post and all comments, I think I know what you want. You just want that the loop continues without stay in this function during the delay right?

millis() does not sleep or delay, it just gives you the time in milliseconds, since the Arduino is running.

So, you can just add this to your code and it will work:

uint8_t level[] = {0, 28, 48, 60, 64};  //levels 0 to 4
unsigned long lastTime = 0;  // << add this
uint8_t start_point = 0; // << move here

void update_leds(uint16_t wait, uint8_t led_num) {
    if(start_point >= led_num) return;
    if(millis() - lastTime > wait) {  // << add this
        //uint8_t start_point;
        lastTime = millis(); // << add this

        //for (uint8_t i = start_point; i < led_num; i++) {
            strip.setPixelColor(start_point, strip.Color(0, 0, 255));
            strip.show();
            //delay(wait);  // << remove this
        //}
        start_point++;
    }
}

void level_up(uint8_t led_num) {
    if (led_num == level[1]) start_point = 0;   //up from level 0 to 1
    if (led_num == level[2]) start_point = 28;  //up from level 1 to 2
    if (led_num == level[3]) start_point = 48;  //up from level 2 to 3
    if (led_num == level[4]) start_point = 60;  //...
}

change wait from uint8_t to uint16_t, since 255 could be too little.

Now, you can call this function many times, but the leds are updated only when the timeout finished.

There is only one problem: if inside your loop you have other delays, maybe the leds are updated some milliseconds later than expected... If you understand what I mean.

Edit: some times, you want also be notified if the leds was updated. So you can return a bool to say if the function updated the leds or not (maybe you need it in the loop, to check if the it "leveled up".

Adriano
  • 1,743
  • 15
  • 28
  • Thank you, Adriano. It is almost perfect, but I need to turn the LEDs on one by one inside the for loop and what you did has no "delay" inside the loop, so it counts very fast and turns all LEDs in the right level[] on simultaneously – KD Technology Nov 04 '19 at 09:45
  • Yes, I know... It is because I don't know how your loop is written. I changed the code a little bit. You have to set your start_point variable inside the loop (once) and then call level_up, until it is terminated (you can for example check if start_point is bigger than led_num). I don't know if this version is ok for you, since there are different ways to solve it. – Adriano Nov 04 '19 at 10:11
  • It turns only the first LED of each level[] like the start_point++ isn't working. My loop always looks for button pushes. The red button is minus one and the blue button is plus one. The score is between zero to four. – KD Technology Nov 04 '19 at 11:02
  • I added the loop. look up :) – KD Technology Nov 04 '19 at 11:05
  • Now it is easier :) Update only `start_point` in your `level_up` function. And continue to call `update_leds` in your loop. Then it should work, without blocking anything :) – Adriano Nov 04 '19 at 11:19
  • And where will you call level_up() to change the start_point? It also needs an input. Can you please edit your code suggestion fully (with my loop function)? – KD Technology Nov 04 '19 at 11:49
  • You can leave your loop so as it is. You do not need to change it. Just add the line `update_leds()` somewhere inside your loop, so that the leds can be updated. The calls to level_up() will change the start_point, so only the start_point is updated, but the leds are updated in the loop now (if you cann `update_leds()` there. – Adriano Nov 04 '19 at 11:53
  • It doesn't work correctly. The LEDs are not stopping when they need to. It just turns everything on in a sequence... – KD Technology Nov 04 '19 at 12:05
  • Yes, but this is exactly what your code will do. Turns every led (one by one) on. Until you update the value of start_point again. – Adriano Nov 04 '19 at 12:30