Valentine's Day POV display using ATtiny13 and Arduino IDE

Valentine’s Day POV display using ATtiny13 and Arduino IDE

Valentine's Day Chocolates with a message!

Valentine’s Day Chocolates with a message!

There’s nothing out there that says “I love you!” quite like a box of chocolates that actually says “I love you” (or “I ❤ U” as it were). Here is a quick electronics project that takes less than 2 hours to complete and makes a nice Valentine’s Day present. It is based on what has lately become my favorite small MCU – Atmel Attiny13 – and the software was written using my favorite development environment – Arduino IDE. So, keep reading for the schematic, the software, the build pictures and a small video showing the message slightly better than a still picture can

Valentine’s is just around the corner, so for all of you procrastinators out there: if you want to build this, first check your electronics parts stock for this list of easily sourced parts:

  1. 1 x Atmel ATtiny13 MCU – can use either PDIP8 (8P3 version) or SOIC-8 ( 8S2 – EIAJ SOIC 0.209″ wide SMT part or S8S1 – JEDEC SOIC .150″ wide SMT ) – the ATtiny13 chips come in many flavors and it’s best to figure out which one you’ll be using because this project can be built around the MCU. The following project description assumes the through-th-hole PDIP body. If you are using the cheaper and much smaller SMT versions of ATtiny13 (a great idea, BTW!) – check the other LED blinker I described in the other post here for the chip mounting and circuit building ideas.
  2. 1xCR2032 or another 3V battery holder plus the battery itself of course. This part is quite critical not only because the circuit obviously needs power but also because it’s the largest, heaviest and bulkiest part of the circuit and you may want to see how you’re going to mount it inside the box and which type of the holder works better. I went for a vertical battery holder which was designed to stand up perpendicular to a PCB but worked just fine in my PCB-less build (see pics below)
  3. 6 x Red LEDs – I’m sure everyone’s parts bin has a few (dozens), just make sure they are the newer high-intensity (5000mcd+) ones because they need to be able to shine through the red plastic box liner.
  4. Oscillating POV display parts and tools

    Oscillating POV display parts and tools

  5. 1 x ball tilt (shaking) switch – this is the part that enables the MCU to go asleep while the box is laying on a table and come alive when someone takes it in their hands.
  6. 1 x 14-pin IC socket. Sure, ATtiny13 is an 8-pin IC but the extra pin sockets will be used to mount other parts, so there’s no contradiction here :)
  7. A perf board (a blank development PCB) for the LEDs – the equal distance between the LEDs is important for POV displays and we’ll mount all 6 LEDs on the perf board so they are spread uniformly.
  8. 3 x 22 Ohm resistors – current limiters for the LEDs
  9. 1 x 470 KOhm – pullup resistor for the ball tilt switch. The higher the value, the less current flows when the switch is closed – useful for longer battery life.
  10. about 3′ of 30AWG wire, preferably red color so it’s easier concealed in the red box
  11. 1 x 6-pin dual rows male header for the ICSP connector. Optional, only if you need to program the ATtiny13 in-circuit
    • Here is a short video of the persistence of vision effect and the message programmed in the display. It is a bit easier to see it on a test circuit I built for easier programming and debugging. Also, in the final version of the software the heart symbol is actually filled in (as on the photos with the chocolate box) but the rest of the message looks exactly like you see on this video below:

      The circuit schematics for this project is very similar to that of the previous two LED blinkers – the only difference of course is the amount of Charlieplexed LEDs – I figured 6 LEDs would have been enough for the simple message I wanted to show. This time I also decided to make a board for the project (see picture below), and BatchPCB is making it as I type this, but I have severely underestimated the time it takes to make and ship a PCB from China around the time of the Chinese Lunar New Year, so to build this project in time for Valentine’s, I had to revert back to the freeform PCB-less builts like the previous LED blinker project here and here

      The circuit schematics and the board:

      Oscillating POV display circuit diagram

      Oscillating POV display circuit diagram


      Attiny13 Oscillating POV display PCB

      Attiny13 Oscillating POV display PCB

      Building it

      Mounting LED at proper height on the POV LED module

      Mounting LED at proper height on the POV LED module

      I started with building the LED module on a strip of perforated breadboard PCB. As you can see, the LEDs are not on one vertical line. They have to shine through the red plastic liner in the box. The liner is not flat, it has peaks and valley (in which the chocolates sit) and I knew from my earlier projects that if the distance between the LED and the material of the liner is different, it will result in the different size of the spot of light at the front surface of the liner – the further away the LED the larger the light spot. For POV effect to work, the light spots have to be as uniform as possible, so I ended up tracing the profile of one of the spots that was long enough to put 6 LED in it, and formed the legs of the LEDs to the required height, drawn on the piece of paper under the board on the left.

      Complete LED strip module with current limiting resistors and other wiring

      Complete LED strip module with current limiting resistors and other wiring

      I used the space left on the PCB for mounting the current limiting resistors and all of the wiring that connects Charlieplexed LEDs to each other. This is pretty much the most cumbersome part of any Charlieplexed LED project and if you have that out of the way, the rest of the circuit will be very easy.

      POV display wire MCU power from the battery holder

      POV display wire MCU power from the battery holder

      Next step is to wire the the battery holder to the proper pins on the IC socket. Keep in mind that the 8-pin ATtiny13 will sit in the 14-pin socket moved to one side of it and the extra 6 pins will be used for mounting the ball tilt switch and its pull up resistor. So, the positive side of the battery actually goes to pin #11 on the socket and the negative – #7. This is the only part of the wiring where I have used blue wire – it would be pretty bad to switch the polarity and so I’ve used it to indicate the negative wire. Positive and negative terminals of the battery holder are clearly marked so you know which wire goes where.

      POV display MCU socket wiring

      POV display MCU socket wiring

      Here we solder the 470K Ohm resistor and the ball switch (facing away from the camera, sorry, not a great angle) as well as the battery and the three wires to the LED strip we’ve prepared earlier. The 30AWG wire I used is so thin that I pretty much just wire-wrapped the legs of the IC socket with the 30gauge wires and soldering them once wrapped was a breeze. When you are done soldering all the connections on the IC socket, I would recommend bending all the legs of it up to lower the profile of the completed unit – the box is small and space is at premium.

      POV display position parts before gluing in place

      POV display position parts before gluing in place

      Picture on the left shows the three parts of the complete device positioned in place just before they were hot-glued in place. Note the location of the LED strip (top left-center) – this was one of the only two “valleys” in this box long and straight enough to house six 5mm LEDs in a row and the LEDs’ heights were profiled accordingly, so this is the only place in the box to put it in, The rest of the part can go anywhere the space is large enough to accept them. And important note about the positioning of the ball tilt switch. As will all my LED blinker projects that include motion detection, the switch should be positioned so that the ball rolls into the part of the switch furthest from the contacts when the box is in it’s normal rest position (flat on the table). In this case there’s no current flowing through the switch, which is ideal for power consumption. When someone picks the box up, the ball rolls back towards its leads and closes the contact, so the current starts flowing. It’s not a huge current (which is why we picked a high value 470K resistor) but it’s better to be avoided anyway.

      Also important is that the software times each scan of the message to the ball tilt switch closing event – because it’s an oscillating POV display (unless you can continuously rotate the box with your hand, it had to be!), you don’t want to show the message on the way back because it will be flipped horizontally (i.e. mirror image). Because all three symbols used – I, and U are all symmetrical, perhaps it would not be too bad to show a message that would read something like I ❤ U U ❤ I – sounds rather sweet (although does not make sense grammatically) – but I wanted a more universal display that could show some other symbols, too, and those may not be symmetrical. So the software uses the ball tilt switch to watch for the proper direction of the swing and so the ball switch should be facing toward the direction in which the message will be shown – i.e. to the right hand side when the box is upright. Note that on the pictures it’s upside down – just to eliminate any confusion.

      When you are gluing the battery holder in place, keep in mind that you may want to change the battery in the future, so leave the top opening of the battery clear of any glue residue. Same goes for the ATtiny13 – you may want to avoid gluing it permanently just in case you need the chip in the future. Use only low temperature hot glue sticks or the glue may melt the thin plastic liner.
      When you’re done with the hot glue, flip the box over, load with chocolate and give it to someone who deserves it!

      POV Display Ready

      POV Display Ready

      A small note on actually using the device – the software is trying to find the average duration of the swing (back and fourth) and starts the scan after 1/8th of the complete cycle has passed since the ball switch had its contact closed (and de-bouncing time expired) i.e. 1/4th of one forward swing, which was my crude attempt at centering the message inside the width of your swing. This works when you’ve already moved it back and forth three-four times but it may make the first couple of swings look weird (squished) – that’s because the average time has yet to calculate properly. Since it starts from zero, it needs a few iterations to resemble the actual average.

      Several things could have been done to alleviate this issue but I have hit a very hard limit in ATtiny13 – the amount of flash memory available for the program. The sketch below compiles to 1006 bytes and ATtiny13 can only keep 1024, so I was really counting individual bytes here and could not avoid the issue of average swing duration. Once something crude and simple like what’s in there could actually fit into the memory. I would be delighted if someone takes the hardware and this early version of the software and comes up with a way to pretty the display a little – and finding the proper time to start the scan every time is the first issue I would start looking at if you want to improve the project.

      As I mentioned, the sketch uses up 1006 bytes out of 1024, and so we can fit 18 more lines of scan in there which can enable slightly longer messages, such as I ❤ NYC and perhaps even I ❤ BEER. Not sure about the latter, but I tried to fit my wife’s name in there (5 letters) and couldn’t, so I just stopped trying. But if you’re looking for more like St. Patty’s type display than St. Valentine’s :) , you can easily replace red LEDs with green and try to spell I ❤ BEER following the easy coding instructions in the comments to the sketch – do let me know if it worked! :)

      If you need help loading Arduino sketches into ATtiny13 MCUs, see this post on using Attiny13 with Arduino IDE here.

      Arduino Sketch for Charlieplexing_Tiny13_POV_6_LEDs and Eagle schematics and board files

      Above is the zipped sketch and supporting Eagle schematics and board files (note that I haven’t received the boards yet so it’s not yet a proven design). And here below is the listing of the sketch’s C program – please read the comments for any customization ideas (such as those other messages for example). If you end up making your own version of this project, I would be delighted to see it! Please be sure to contact be or post a message in the Projects board of the forums.

      /*
        Program code for the Pimp Your Chocolates project. 
        Charlieplexing 10 LEDs mounted into a chocolate box using ATtiny13 with some brightness control using pseudo-PWM
        
        The tilt switch motion sensor is connected to Pin 2 (PB3)
        See http://elabz.com/valentines-day-pov-display-using-attiny13-and-arduino-ide/
        for the complete circuit schematics
        
        Code by Elabz.com
      
      http://elabz.com/
      
        This example code is in the public domain. If you end up using it in a project, please drop me a message, I'd be happy to
        know it was of some use. I'll also be happy to feature your project on my site, so send some pictures, too.
        
        LED hookup can be gleaned from the bitmap[] array values. For example, first LED's value is B00000001 which means that
        to light the LED the D0 has to ho HIGH and D1 - LOW, so the LED's anode is facing D0. LED #2 is between the same legs but in reverse. 
        LED #3 is between D0 and D2, LED #4 is the same legs but in reverse and so on. 
        
      //       ATMEL ATTINY13 / ARDUINO
      //
      //                 +-\/-+
      // ADC0 (D 5) PB5 1|    |8 Vcc
      // ADC3 (D 3) PB3 2|    |7 PB2 (D 2) ADC1
      // ADC2 (D 4) PB4 3|    |6 PB1 (D 1) PWM1
      //            GND 4|    |5 PB0 (D 0) PWM0
      //                 +----+
      
        
        
       */
      #include <avr/pgmspace.h>
      
      #include <avr/sleep.h> // sleep code by insidegadgets.com
      #ifndef cbi
      #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
      #endif
      #ifndef sbi
      #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
      #endif
      
      
      byte bitmap[] PROGMEM ={0B00000001,0B000000010,0B00000010,0B00000100,0B00000100,0B00000001};
      byte outModes[] PROGMEM ={0B00000011,0B00000011,0B00000110,0B00000110,0B00000101,0B00000101};
      
      
      PROGMEM byte column[17]   = {
      
        33,    // --X----X // 0
        63,    // --XXXXXX // 1
        33,    // --X----X // 2
        0,     // -------- // 3
        24,    // ---XX--- // 4
        60,    // --XXXX-- // 5
        30,    // ---XXXX- // 6
        15,    // ----XXXX // 7
        30,    // ---XXXX- // 8
        60,    // --XXXX-- // 9
        24,    // ---XX--- // 10
        0,     // -------- // 11
        62,    // --XXXXX- // 12
        1,     // -------X // 13
        1,     // -------X // 14
        62,    // --XXXXX- // 15
        0      // -------- // 16
      
      };
      
      //byte d=HIGH;
      byte y;
      byte x;
      byte z=1;
      byte ledDelay=1; // microseconds to show one LED
      unsigned int showTime=10000;
      byte debounceDelay = 50000; // microseconds
      byte p=LOW; // last state
      unsigned int swingTime;
      unsigned int swingTimeAverage;
      unsigned int swingDelay;
      unsigned long swingDelayNow;
      unsigned long swingTimeLast;
      
      void setup() {
      // pinMode(3, INPUT); 
      //  sbi(GIMSK,PCIE); // Turn on Pin Change interrupt
      //  sbi(PCMSK,PCINT3); // Which pins are affected by the interrupt
      swingTimeLast =  millis();
      }
      
      void loop() {
      unsigned long showNow;
      unsigned  long periodNow;
      unsigned  long lastDebounceTime;
      byte currentColumn;
      byte currentLED;
      byte mask=1;
      
      showNow = millis();
      while(millis()-showNow < showTime)
      {
      
      int readNow=digitalRead(3); // 
      if(readNow != p)
      {
        lastDebounceTime = micros();
        
      }
      
        if ((micros() - lastDebounceTime) > debounceDelay) {
          // whatever the reading is at, it's been there for longer
          // than the debounce delay, so take it as the actual current state:
          //DDRB = 0B00000000; // turn everything off
          p=readNow;  
      if(p==HIGH){ // show symbols only on one way
        y=0;
       
      if(z==0){
       
       delay(swingDelay);  // delay showing because the beginning of the cycle is where the symbol does not look right (speed to slow)   
        while(y < 17){
            x=0;
            currentColumn = pgm_read_word_near(&(column[y])); 
            mask=1;
            while(x<6){
               if(currentColumn & mask){
               PORTB = pgm_read_byte(&(bitmap[x])); // turn LED on
               DDRB = pgm_read_byte(&(outModes[x]));
               }else{
               DDRB = 0B00000000; 
               } 
              if(micros()-periodNow > ledDelay)
              {  
              periodNow = micros();
              x++;
              mask <<= 1;
              }       
            }
          y++;
        }
      DDRB = 0B00000000;
        swingTime = millis()-swingTimeLast;
        swingTimeAverage = (swingTimeAverage+swingTime)/2;
        swingDelay=swingTimeAverage/8;
        ledDelay = swingTimeAverage/4; // this only works because ledDelay is in microseconds, so it's 
        swingTimeLast=millis();
      
      }
      z++;    
      
      //}
      } else {
        z=0;
        
      }
      
      } 
        //    DDRB = 0B00000000; // dont show anything on the way back else it will be mirrored!
         
        
      }
      
       DDRB = 0B00000000; // turn everything off at the end of the show
       sbi(GIMSK,PCIE); // Turn on Pin Change interrupt
       sbi(PCMSK,PCINT3); // Which pins are affected by the interrupt
       system_sleep();
      
      
      
      }
      
      // From http://interface.khm.de/index.php/lab/experiments/sleep_watchdog_battery/
      void system_sleep() {
        cbi(ADCSRA,ADEN); // Switch Analog to Digital converter OFF
        set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Set sleep mode
        sleep_mode(); // System sleeps here
        sbi(ADCSRA,ADEN);  // Switch Analog to Digital converter ON
      }
      
      ISR(PCINT0_vect) {
      
      }
      

3 Responses to “Valentine’s Day POV display using ATtiny13 and Arduino IDE”

  • smeezekitty:

    Looks awesome.
    Not sure where to get a ball tilt switch though.

    byte debounceDelay = 50000; // microseconds

    uhh… Not sure what to make of that

    • I got the first one from a Brita (or was it PUR?) water filtering jar. It was used for counting tilts of the jar when you pour water to tell when the filter needs changing. Then eBay of course. $0.50 each from China.

      Re: byte debounceDelay = 50000; // microseconds good catch! I wonder if it assigned 255 or 0 due to wrong type? I’ll go change it to int and see what happens. I have a feeling I’m going to need 5,000 mks (5ms) for debouncing not 50,000 if even that.

  • [...] Patrick’s Day is coming up and I thought it’s already time to update the original Valentine’s Day POV display with a new message and make use of the second PCB I had done at BatchPCB. Also, fortunately, John [...]

Leave a Reply

Or use the Forums ! If your comment is a question, please consider posting it to a matching section of our Electronics Forums. The forums allow for a more natural conversation flow, especially if multiple replies are required. Additionally, you'll be able to style your writing (bold font, italics etc.) and post images which can help with a good answer.

Notify me of followup comments via e-mail. You can also subscribe without commenting.

Tools

Coming soon ...

Recent Comments
  • Ezequiel França dos Santos: Thank you very Much!
  • Deives: Hello friend, first I would like to congratulate you on an excellent project. Well I am Brazilian and I have...
  • admin: Well, I did give it some thought and have been meaning to try for some time now. But, in general, the approach...
  • Adi Soffer: Hey there. Great post and thank you for sharing. I’m trying to figure out how to control a...
  • Tony: So many thanks!
  • admin: Thanks for stopping by, Tony. I’ve posted an LTSpice model of the driver some time ago, check it out:...
  • Tony: a little question: I am using Multisim to simulate your circuit and what load should I use? When I use a...
  • admin: The sensor itself is fine, I’ve used exactly the same. They are incredibly simple devices – just a...
  • Phil: Thanks you for your anwser. I think I have understood the usage you make of interupts. But after tring several...
  • admin: I think the interrupt implementation is the same in ATtiny85 (although I have to admit I haven’t checked...
Meta