【问题标题】:how to use millis() instead of for loop in this function如何在此函数中使用 millis() 而不是 for 循环
【发布时间】:2019-11-04 06:23:26
【问题描述】:

我使用 Neopixels(64 个 LED),并且我有一个名为 level_up 的函数,每次都会获得不同的 led_num。一般是水平条; level[1] 将按从 0 到 28 的顺序点亮所有 LED,level[2] 将点亮从 29 到 48 的所有 LED,依此类推。 我附加的功能工作正常,但我需要将延迟更改为 millis() 并且不知道如何。有什么想法吗?

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);

}

【问题讨论】:

  • delay()中的参数是毫秒。
  • ... 和 delay()s 参数是 unsigned long。您的 wait 变量仅允许 255 ms 长时间延迟。
  • 我知道;我不需要更多:) 我的问题是关于更改此函数以使用 millis()。
  • millis() 仅报告自您开始以来的毫秒数。你想用它做什么?
  • 我需要这个函数与 Arduino 执行的其他操作一起进行多任务处理。延迟会停止处理器,而 millis() 或计时器则不会。

标签: c++ c arduino neopixel


【解决方案1】:

使用millis() 不会像delay() 那样阻塞for 循环。 因此,我认为您将不得不调整调用您的方法的代码,因为目前看起来您的代码取决于在 for 循环中被阻塞。 但通常你会使用millis(),就像下面的代码示例一样。您存储开始时间戳,然后在等待期结束后执行某些操作。

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);

【讨论】:

  • 我试过这个,但它没有停止点或与 level[] 的连接。
  • 我有两个按钮。每次按下它们都会增加或减少级别[i]。如果我使用 counter++,我需要将边界添加到下一个或上一个级别 [i],否则它将穿过整个 LED 灯条....
  • 谢谢。我稍后会检查它
【解决方案2】:

这并不能直接回答您的问题,但我使用的策略为我提供了任意数量的定时事件,而我的程序不会在 millis() 中阻塞。

在未来设置一个截止日期,并将延迟的操作包含在一个 if 语句中,该语句会轮询 millis(),直到达到该截止日期。这并不完美,因为软件计时会因处理而损失时间,以及由于 millis() 溢出和环绕问题(在 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)
    }

【讨论】:

    【解决方案3】:

    在阅读了您的帖子和所有 cmets 之后,我想我知道您想要什么。您只想在延迟期间继续循环而不停留在此函数中吗?

    millis() 不会休眠或延迟,它只会以毫秒为单位为您提供时间,因为 Arduino 正在运行。

    因此,您只需将其添加到您的代码中即可:

    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;  //...
    }
    

    将等待从 uint8_t 更改为 uint16_t,因为 255 可能太少了。

    现在,您可以多次调用此函数,但只有在超时结束时才会更新 LED。

    只有一个问题:如果在你的循环中你有其他延迟,也许 LED 更新比预期晚了几毫秒......如果你明白我的意思。

    编辑:有时,如果 LED 已更新,您也会收到通知。因此,您可以返回一个布尔值来说明函数是否更新了 LED(也许您需要在循环中使用它,以检查它是否“升级”。

    【讨论】:

    • 谢谢你,阿德里亚诺。它几乎是完美的,但我需要在 for 循环内一个接一个地打开 LED,而你所做的在循环内没有“延迟”,所以它计数非常快,并同时打开正确级别的所有 LED []
    • 是的,我知道...这是因为我不知道您的循环是如何编写的。我稍微更改了代码。您必须在循环内设置 start_point 变量(一次),然后调用 level_up,直到它终止(例如,您可以检查 start_point 是否大于 led_num)。我不知道这个版本是否适合你,因为有不同的方法来解决它。
    • 它只打开每个级别的第一个 LED[],就像 start_point++ 不工作一样。我的循环总是寻找按钮按下。红色按钮是减一,蓝色按钮是加一。分数在零到四之间。
    • 我添加了循环。查找:)
    • 现在更容易了 :) 仅在您的 level_up 函数中更新 start_point。并继续在您的循环中调用update_leds。然后它应该可以工作,不会阻塞任何东西:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-27
    • 1970-01-01
    • 2022-01-17
    • 1970-01-01
    • 1970-01-01
    • 2021-01-29
    相关资源
    最近更新 更多