Heartbeat

This sketch is a variation of the approach I have used already for the Knight Rider sketch. It was inspired by the Heartbeat thread in the German Arduino Forum. The goal is a double blink that approximates a heartbeat. Volvodani who came up with the idea implemented it with one LED. Since my shield provides 20 LEDs I extended the approach slightly. Instead of fading in and out this hearbeat effect will expand and contract as well.

//
//  www.blinkenlight.net
//
//  Copyright 2012 Udo Klein
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program. If not, see http://www.gnu.org/licenses/


#include <MsTimer2.h>
#include <avr/io.h>
#include <avr/pgmspace.h>

const uint8_t step_delay = 20;

const uint8_t ports = 3;
const uint8_t brightness_levels = 32;


#define brightness_by_phase(phase) \
    (phase==0?  1: \
     phase==1?  2: \
     phase==2?  3: \
     phase==3?  4: \
     phase==4?  6: \
     phase==5?  8: \
     phase==6? 12: \
     phase==7? 16: \
     phase==8? 24: \
               31)

#define LEDIndex(LED) (LED>9?LED-10: 9-LED)

#define brightness(LED, phase) (LEDIndex(LED)<=phase? brightness_by_phase(phase): 0)

// cycle will loop from 0 to brightness_level
#define LEDstate(LED, phase, cycle) (cycle>=brightness(LED, phase)? 0: 1)

#define PortstateC(phase, cycle) (    \
    (LEDstate(14, phase, cycle)   ) + \
    (LEDstate(15, phase, cycle)<<1) + \
    (LEDstate(16, phase, cycle)<<2) + \
    (LEDstate(17, phase, cycle)<<3) + \
    (LEDstate(18, phase, cycle)<<4) + \
    (LEDstate(19, phase, cycle)<<5) )

#define PortstateB(phase, cycle) (    \
    (LEDstate( 8, phase, cycle)   ) + \
    (LEDstate( 9, phase, cycle)<<1) + \
    (LEDstate(10, phase, cycle)<<2) + \
    (LEDstate(11, phase, cycle)<<3) + \
    (LEDstate(12, phase, cycle)<<4) + \
    (LEDstate(13, phase, cycle)<<5) )

#define PortstateD(phase, cycle) (    \
    (LEDstate( 0, phase, cycle)   ) + \
    (LEDstate( 1, phase, cycle)<<1) + \
    (LEDstate( 2, phase, cycle)<<2) + \
    (LEDstate( 3, phase, cycle)<<3) + \
    (LEDstate( 4, phase, cycle)<<4) + \
    (LEDstate( 5, phase, cycle)<<5) + \
    (LEDstate( 6, phase, cycle)<<6) + \
    (LEDstate( 7, phase, cycle)<<7) )

#define Ports(phase, cycle) PortstateC(phase, cycle), PortstateB(phase, cycle), PortstateD(phase, cycle)
#define pwm(phase) \
    Ports(phase,  0), Ports(phase,  1), Ports(phase,  2), Ports(phase,  3), \
    Ports(phase,  4), Ports(phase,  5), Ports(phase,  6), Ports(phase,  7), \
    Ports(phase,  8), Ports(phase,  9), Ports(phase, 10), Ports(phase, 11), \
    Ports(phase, 12), Ports(phase, 13), Ports(phase, 14), Ports(phase, 15), \
    Ports(phase, 16), Ports(phase, 17), Ports(phase, 18), Ports(phase, 19), \
    Ports(phase, 20), Ports(phase, 21), Ports(phase, 22), Ports(phase, 23), \
    Ports(phase, 24), Ports(phase, 25), Ports(phase, 26), Ports(phase, 27), \
    Ports(phase, 28), Ports(phase, 29), Ports(phase, 30), Ports(phase, 31)


PROGMEM uint8_t const pov_pattern[] = {
    pwm( 0), pwm( 1), pwm( 1), pwm( 1), pwm( 2),
    pwm( 2), pwm( 3), pwm( 3), pwm( 4), pwm( 4),
    pwm( 5), pwm( 5), pwm( 6), pwm( 6), pwm( 7),
    pwm( 7), pwm( 8), pwm( 8), pwm( 9), pwm( 9),
    pwm( 8), pwm( 7), pwm( 6), pwm( 5), pwm( 4),
    pwm( 5), pwm( 6), pwm( 7), pwm( 8), pwm( 9),
    pwm( 9), pwm( 8), pwm( 8), pwm( 7), pwm( 7),
    pwm( 6), pwm( 6), pwm( 5), pwm( 5), pwm( 4),
    pwm( 4), pwm( 3), pwm( 3), pwm( 2), pwm( 2),
    pwm( 1), pwm( 1), pwm( 1), pwm( 0), pwm( 0),
};


volatile uint16_t base_index = 0;

void iterate() {
    base_index += ports*brightness_levels;
    if (base_index >= sizeof(pov_pattern)) { base_index = 0; }
}

void setup() {
    DDRD = 0b11111111; // set digital  0- 7 to output
    DDRB = 0b00111111; // set digital  8-13 to output
    DDRC = 0b00111111; // set digital 14-19 to output (coincidences with analog 0-5)

    MsTimer2::set(step_delay, iterate);
    MsTimer2::start();
}

void loop() {
    static uint16_t index;
    cli();
    index = base_index;
    sei();

    for (uint8_t cycle=0; cycle<brightness_levels; ++cycle) {
        PORTC = pgm_read_byte(pov_pattern+(index++));
        PORTB = pgm_read_byte(pov_pattern+(index++));
        PORTD = pgm_read_byte(pov_pattern+(index++));
    }
}

As you can see from the code the overall approach is very similar to the Knight Rider Without Flicker experiment. The main difference is that this code does not “unroll a loop” anymore. Have a look at the array contents

PROGMEM uint8_t pov_pattern[] = {
    pwm( 0), pwm( 1), pwm( 1), pwm( 1), pwm( 2),
    pwm( 2), pwm( 3), pwm( 3), pwm( 4), pwm( 4),
    pwm( 5), pwm( 5), pwm( 6), pwm( 6), pwm( 7),
    pwm( 7), pwm( 8), pwm( 8), pwm( 9), pwm( 9),
    pwm( 8), pwm( 7), pwm( 6), pwm( 5), pwm( 4),
    pwm( 5), pwm( 6), pwm( 7), pwm( 8), pwm( 9),
    pwm( 9), pwm( 8), pwm( 8), pwm( 7), pwm( 7),
    pwm( 6), pwm( 6), pwm( 5), pwm( 5), pwm( 4),
    pwm( 4), pwm( 3), pwm( 3), pwm( 2), pwm( 2),
    pwm( 1), pwm( 1), pwm( 1), pwm( 0), pwm( 0),
};

For the Knight Rider Experiment the entries were 0..37, basically an unrolled loop. Thus the whole effect had to be derived from this “unrolled loop counter”.

Now I directly store the different stages the effect will cycle through. This makes it much easier to fine tune the results because the functions will now operate on the stage number instead of the loop counter. Technically no big difference but definitely simpler to pull off. The main reason that makes the whole thing simpler to control is that the “phase” now has slightly more semantics than before. As a bonus it reduces the computational load of the macro processor and the compiler.

Check out the video for to see the final result.

4 Responses to Heartbeat

  1. For strange reason the code contains contained undesired markup. I will fix this tomorrow. I fixed this. Sorry for any inconveniencies. This is the third time this happened. No clue how and why some of my articles got messed up like this. I definitely do not paste HTML around. If anyone has any hints for me I would be grateful for a clarification.

  2. Andreas Storch says:

    Heed help!! Hello, I’m an absolut beginner in Arduino programming and bought the blinkenlight shield for first steps. Trying out “knightrider” it worked without problems, but I can’t get “heartbeat” to run. I always get the message in function ‘void setup()’ “112 error ‘MsTimer2’ has not been declared” and “113 error: ‘MsTimer2’ has not been declared”. The sketch I’m using is

    #include
    #include
    #include

    What can I do?

    Thank you and best regards.

    Andreas Storch

  3. Andreas says:

    Hi Udo,

    thanks for your quick response. Yes, you were right. It was the missing copy of msTimer2 in the Arduino libary. I’m sure it is a common problem for beginners. Thanks for your help.

    Andreas

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.