Each of these mini-projects has one thing in common, this little microcontroller: the ATtiny. Now, I’ve made three of them since they will be given as Christmas gifts, but they are all a little different in their own ways. Anyways, the point of this video is for you to learn about the ATtiny, but also some more advanced AVR programming topics, namely sleep mode and flash memory. And as an added bonus, you’ll learn how to make these projects as a whole. So, without further ado, let’s dive in.
Minimum Required Circuit
For this video, we will focus on the ATtiny85 specifically. Let’s setup the bare minimum circuit. And it’s really very simple. It’s literally just VCC, GND, and a 10k pull-up on RESET. You can place a crystal on the XTAL pins if you need one, but for what we are doing, it’s not necessary. You can add your programmer’s pins as needed. Beyond this point, everything that we will add is application specific and is not required.
Project 1/3: Vader
Here’s an interesting piece of history. This was one of my first electronics projects. I made this for an engineering class about five years ago. As you can see, fifteen-year-old me wasn’t nearly as competent at soldering or circuit desing in general. So, what we will do today is refurbrish the circuit inside. What I’d like to do is give the LEDs a breathing pattern sort of like what I did back in the PIC video.
To make this work, we have two options. We could either use software to run our PWM signal or we can use one of the timer peripherals to generate an appropriate PWM signal. For this project, I went with the timer-based PWM method since it will make our main loop cleaner and easier to read. For this purpose, I went with timer0. This timer has several modes of operation, but we will stick with the fast PWM mode.
Basically, in fast PWM mode, the timer counts up like normal, but when the timer’s value is equal to the value in the output compare register (OCR), then the output pin is pulled low. Once the timer resets, the output pin is pulled high. Let’s setup the hardware for this. The PWM output will be placed on OC0B, which is on the same pin as PB1. So, what I did was placed a resistor into the base of an NPN transistor. This transistor will act as our switch for all of the LEDs.
Let’s get into the code that I have written to get the timer’s PWM up and running. The first line is modifying the TCCR0A register. Here we have to modify the WGM bits. If you take a look at the table in the datasheet, you’ll find a list of modes based on these three bits. As you see, I selected the last option, which is our Fast PWM mode. Now, to get our PWM signal out onto the pin, we need to do two things. First, set the COM0B1 bit in the TCCR0A register, this will enable a non-inverting PWM output on the OC0B pin. Next, we need to set that PWM pin as an output, which corresponds to PB1 on DDRB. Now we can finally test everything by enabling the clock without a prescaler using the TCCR0B register.
As you can see, we have a 50% duty cycle on our output. And we can change it by modifying the OCR0B register. But we want that breathing effect. To do that, we can enable the timer0 overflow interrupt. Now we can change the PWM duty cycle in the interrupt. I’ve used a struct to hold information about the current PWM settings. The duty_cycle and direction variables should be a little obvious. They simply keep track of the current duty cycle and which direction it should go next. The post_scale variable is there for me to provide a software postscaler. Basically, I want the interrupt to activate once every 256 timer overflows. I also added a helper function that simply converts a duty cycle that is between 0 and 100 to the corresponding percentage of 255 for the timer.
Beyond that, the interrupt simply either counts up or down depending on the direction. However, as you can see, the linear approach doesn’t look the greatest, because it looks like the LED is ON most of the time. To fix this I made a scaling function. Basically, at the lower duty cycles, the change either up or down is very small. But at the higher duty cycles, the change is much larger. This essentially balances out the bright and dim periods of the LED. And as you can see, our LED is breathing properly.
Now, as for the phyiscal construction, since this is a project that I made so long ago I no longer have access to the tools that I used to make this. So I can’t demonstrate it on camera unfortunately. The biggest thing that you have to know about this is that the acryllic at the top here was carved using a laser engraver. The box was cut using a laser engraver as well. But let’s take a moment from the box and get to soldering. I simply wired three red LEDs up in parallel and then placed them all in series with the collector the of transistor. I also added button to toggle the whole thing on and off, but I’ll talk more about that in the next project. And finally, I added a USB port to supply 5V power to the circuit.
To complete the project, we simply just have to drill some big holes for the new USB port and button. As you can see, the finished product looks really cool, especially when you turn off the lights.
Project 2/3: Bitcoin Ornament
This next project was made for my brother who is obsessed with bitcoin. When we press the button, the LEDs breathe. It looks very similar to the Vader project, but with one key difference: it is battery powered. That’s why the LEDs turn off after a while, since we want to save power. But it goes much deeper than just turning off the LEDs, And this is where I have to explain sleep mode to you.
In sleep mode, we basically turn off parts of the microcontroller. In our case, we turn off most everything to save as much power as possible. In the datasheet, you will find three different sleep modes: Idle mode, adc noise reduction mode, and power-down mode. We are most interested in power-down mode since it essentially turns 99% of the microcontroller off. The only things still active are the BOD (if enabled), the Watchdog timer (if enabled), and the pin change interrupts. At this point, there are very few things that can wake the CPU back up, but we can use an external pin-change interrupt.
The first thing that we will do is enable this pin-change interrupt. To do this, I simply added a button to ground along with a pull-up resistor on PB2. I then enabled pin-change interrupts in the GIMSK register and specified PB2 in the PCMSK register. Now, we will leave the actual Interrupt service routine empty since we aren’t interested in executing code here, we are only interested in the wake-up functionality.
Now let’s work on getting our microcontroller to sleep. Luckily, avr-gcc comes with some handy helper methods for this purpose. Make sure to include the avr/sleep.h header. The bare minimum for sleep functionality goes as follows: first set your sleep mode, we are using the power down mode for this project. Next, sleep_enable(). This doesn’t actually put our microcontroller to sleep, but it allows the next call, sleep_cpu() to put the microcontroller to sleep. Now we stop here until our interrupt wakes up the microcontroller. Then the next line is sleep_disable().
This is good and all, but we need to flesh it out a bit more. We just need to disconnect the PWM line from OC0B so that we don’t leave the LEDs on while the microcontroller is sleeping. We can re-enable it after waking from sleep.
Let’s test the current draws during active mode and sleep mode. To make the test more fair, I removed the LED. In active mode, I measured an average current of about a milliamp. In sleep mode, I measured 86 microamps, which seemed a bit high. Then I remembered to remove the programmer from the circuit. Afterwards, I measured a current of just 0.2 microamps. So it’s definetly very important to keep the microcontroller in sleep mode when not in use for the sake of extending battery life. In the main loop, I simply have it delaying for 10 seconds, then going back into sleep mode.
Now let’s talk about how I made this PCB. The schematic is basically the same as the first project, except we have a lot more LEDs this time. The components are obviously surface mount as well in the 0806 size. There is also a coin cell battery holder on the back. The biggest part difference is the microcontroller. I used an ATtiny25 instead of an ATtiny85. Don’t worry though, these two chips are basically the same thing. The only difference is that the ‘25 has a smaller flash size and RAM size than the ‘85. As long as your code doesn’t use too much memory, it should compile and run exactly the same on both.
Let’s talk about how I got this bitcoin logo onto the silkscreen. For whichever image you choose, you need it to have two main colors other wise you will lose quality. Bitcoin only has white and orange, so it will be fine. Next, get an SVG of your image. You will take it to an image editing program, I personally used gimp. From here open the SVG and then export it as a BMP file. Next, go to KiCad and select image converter. Load your BMP file and adjust the black and white threshold until you get a clear image. You may need to check the negative box to reverse the black and white. Also adjust the size as needed. Next click on footprint, then export to file.
Afterwards click on footprint editor. From here make a new library and name it whatever you wish. Import the file you just created and make any extra edits as needed. I simply unchecked the fill shape box. After you save, you can go to your PCB file and press ‘a’. Search for your new image and place it wherever you wish. After this you can design your PCB as normal.
As for the assembly, I also got the stencil and used it so spread solder paste. Afterwards I placed all of the components and used the hotplate to solder everything. I had to hand solder the programming header and the battery holder on the back. I also made a mistake in the schematic at first. I had the button connected to VCC, so to fix the problem I took a knife and cut the trace. I then used some tiny wire to connect the button to ground. And here’s the final product. I have to say that it makes for a good gift.
And not to leave anyone hanging, I’ll briefly explain the button from the first project. It’s there to toggle the circuit into sleep mode just like the bitcoin. The button interrupts sleep mode and polls to go to sleep.
Project 3/3: Buzzer
This last project is reminiscent of my very first video, but it is an improvement. Luckily it’s not all that much different from the first two projects. The main difference this time is that we need to vary the frequency of the waveform instead of the duty cycle. To make this work, we will have to stop using timer0 and switch over to timer1. Timer1 has an additional register - OCR1C - that allows us to change the TOP value of the timer. This means that if we set a lower TOP value, we will get a faster frequency. As for the schematic, it’s literally the same as the first two, but I added two more LEDs on PB0, but we will talk about those in a minute. Coincidentally, OC1A is on the same pin as OC0B.
Anyways, we need to determine what prescaler we should use to get our desired frequency. You can run a simple calculation to determine your minimum frequency. Divide your system clock (which is 1MHz by default) by 256 and your prescaler value. If we don’t use a prescaler, we get a frequency of 4kHz, but that’s a bit too high. Instead, we can use a 64x prescaler and get a minimum frequency of 61 Hz, which is much more suitable for music.
Speaking of calculating frequencies, we can make a helper function that will select the right OCR1C value for our desired frequency. Simply divide the system clock by the prescaler and the desired frequency. Make sure to also set OCR1A because it controls our duty cycle. I always made it 50%. We can now select arbirtrary frequencies to output. Now to play music, we can select certain frequencies that correspond with musical notes. There’s this helpful webpage that you can use which shows the connections between notes and frequencies. I simply copied them over into a header file to make it easier to type in our code. You can download this header in the description to save yourself the time.
Now let’s try to make a song. I made a Note struct to help with this process. First, we have the frequency as expected, but afterwards we have length. In sheet music there are different notes which correpsond to different lengths, we have eighth notes, quarter notes, half notes, and full notes. So I defined them as 1, 2, 4, and 8. A note can also have a dot, which means that you hold it for another half length. I defined the DOT as the last bit. This allows us to bitwise OR it onto our length.
Let me show you a practical example of a song in action. A song is simply a collection of notes, so we can make an array of our Notes. Let’s make jingle bells. Our first note is D right above middle C, so we type D4. We can see that it is a quarter note so we type that in. The next note is a B4 quarter note. This process continues until we reach this note. This is a dotted half note. So we type in D4 and then HALF, but then we OR in the DOT to represent our dot. Hopefully this process is simple enough to follow.
Let’s now make a helper function to play these notes. The first thing to do is make sure that the frequency isn’t 0, if it is don’t play anything. I made disable_PWM_output and enable_PWM_output to help with these problems. Afterwards we set our frequency to the note’s frequency. The last thing to do is determine how long we should hold this note for. The beat_length variable is the length of a quarter note in milliseconds. So to dynamically determine the length of time we can use this relationship to determine the length of the other notes.
The first half of the equation simply divides beat_length by two to get the length of an eighth note and then multiplies by the type of Note. We AND out the DOT bit for this purpose. The second half is a bit more interesting. We add half of the original length if there is a DOT. I can honestly say that this is the first time that a double NOT has ever been useful in my code. Basically, the purpose of the double NOT is to move our 8th bit down to a one, if it exists, that way we either multiply by one or zero. The NOT operator will return either a 1 or 0 depending on whether or not the value is non-zero. The problem is that it is inverted, so we just invert it again.
After this point, the song actually plays well. So let’s add another song. And, wait a minute, why does the song glitch in the middle of playing? The only change to the code at this point was adding another song to memory. Well, let’s take a second to appreciate just how small the ATtiny’s RAM space actually is. The ATtiny85 only has 256 bytes of RAM, and our new song is taking up too much space. But we still have so much flash avaliable, so gives? Well, the AVR architecture has FLASH on a different address space than the RAM, so the C compiler doesn’t automatically just use everything in FLASH.
But we can manually save our data into FLASH with PROGMEM. This ensures that the song will stay in flash and keep our RAM clean. But, like I said, the FLASH is on a different address space so we can’t access it in the same way as normal. Instead we have to use this method memcpy_P. This will allow us to copy our note in FLASH to a temporary note in RAM. After this change, we can play both songs glitch free. I also added another struct to make everything a bit more organized and it allows us to change our beat_length on a song-by-song basis. To wrap the code up, I made two more songs and added a for-loop to allow us to rotate through all of the songs.
And for assembly, it’s basically the same as the bitcoin. I spread solder paste over the stencil and used the hot-plate to melt the solder. I also made the same mistake with the wake-up button, so I repeated the repair process and cut the old trace and wired up a new connection with that really small wire. And to answer that question. The extra LEDs are there to blink and every note played. It makes for a really cool effect.
There you have it. Whether you’re interested in the christmas theme of everything or not, I hope you’ve learned something new about microcontroller programming. If you’ve enjoyed this video, please consider subscribing so that you can see my future videos. Also, visit my buymeacoffee page. With your support, you can help me make more videos like this one. I’d like to thank Mr. devNull and Cognisent for supporting these videos. Thanks for watching, have a good one!