Introduction: ATtiny Seven Segment Display Timer
This instructables step by step show how to use ATtiny 8-bit Microcontroller port manipulation drive 4 seven segment display as a simple count up timer.
It is a good starting point for learning how the microcontroller and seven segment display works.
Step 1: What Is Seven Segment Display?
Seven segment display is a display that assembly from 7 long shape LED. The different LED on and off combination can represent 0-9 digit and some of alphabet letter.
In most case, the display also have a decimal point LED at the lower right corner, so the seven segment display actually contains 8 LED.
For circuit simplicity reason, 8 LEDs share same anode or cathode connector and represent this feature in their name:
- common anode seven segment display
- common cathode seven segment display
Step 2: Preparation
Seven Segment Display
This time I am using 4 x 0.8" common anode seven segment display.
Microcontroller
This project require a 8-bit IO port for driving the seven segment display and 4 more IO pins for switching between 4 seven segment display. I have some ATtiny861A in hand, it has 16 IO pins, it is good enough for this project.
Breadboard
A 830 tie points breadboard is good enough for this project.
Breadboard Wire
Breadboard's best friend!
Power Source
According to ATtiny datasheet, it can operate from 2.8 V to 5.5 V, this time I am using a AAA size lithium iron phosphate (LiFePO4) rechargeable battery (Nominal cell voltage 3.2 V) along with a breadboard friendly AAA battery holder.
Resistor
4 resistors for seven segment display, depends on your power source voltage. From my trial and error, 47 Ohm works for 3.3 - 5 V. (When powered by 5 V, the LED is a little bit too bright but not yet burn it out :P)
ISP Programmer
Any ISP programmer that can program ATtiny is ok, I am using USBasp this time.
Step 3: Software Preparation
Arduino IDE
Download and Install Arduino IDE latest 1.x version if not yet:
https://www.arduino.cc/en/Main/Software
SpenceKonde/ATTinyCore
Follow the install instruction to add ATtiny microcontroller support:
https://github.com/SpenceKonde/ATTinyCore/blob/mas...
Note
The Clock option 8 MHz may have some bug and the time function is not working, so I am using 1 Mhz option. it also can reduce a little bit power usage.
Step 4: One Digit Trial Run
Let's start from one digit first, here is the connection summary:
ATtiny861
Pin 1 MOSI -> ISP programmer MOSI
Pin 2 MISO -> ISP programmer MISO
Pin 3 SCK -> ISP programmer SCK
Pin 5 VCC -> ISP programmer VCC
Pin 6 GND -> ISP programmer GND
Pin 9 D3/PB6 -> 47 Ohm resistor -> Seven Segment Display Common Anode Pin
Pin 10 RESET -> ISP programmer RESET
Pin 11 D13/PA7 -> Seven Segment Display E Pin
Pin 12 D12/PA6 -> Seven Segment Display D Pin
Pin 13 D11/PA5 -> Seven Segment Display C Pin
Pin 14 D10/PA4 -> Seven Segment Display H Pin
Pin 17 D14/PA3 -> Seven Segment Display G Pin
Pin 18 D2/PA2 -> Seven Segment Display F Pin
Pin 19 D1/PA1 -> Seven Segment Display A Pin
Pin 20 D0/PA0 -> Seven Segment Display B Pin
Step 5: GPIO Style Start From Zero
Let's start from display a zero digit on the seven segment display first, copy the follow code to Arduino IDE and then press upload:
void setup() {
// init GPIO
pinMode(3, OUTPUT); // common anode
pinMode(1, OUTPUT); // segment A
pinMode(0, OUTPUT); // segment B
pinMode(11, OUTPUT); // segment C
pinMode(12, OUTPUT); // segment D
pinMode(13, OUTPUT); // segment E
pinMode(2, OUTPUT); // segment F
pinMode(14, OUTPUT); // segment G
pinMode(10, OUTPUT); // segment digit point
}
void loop() {
// display digit zero
digitalWrite(3, HIGH); // enable common anode
digitalWrite(1, LOW); // segment A on
digitalWrite(0, LOW); // segment B on
digitalWrite(11, LOW); // segment C on
digitalWrite(12, LOW); // segment D on
digitalWrite(13, LOW); // segment E on
digitalWrite(2, LOW); // segment F on
digitalWrite(14, HIGH); // segment G off
digitalWrite(10, HIGH); // segment digit point off
}
Note
- seven segment display common anode pin always require positive power, set the pin HIGH to enable the display
- in contrast, segment pin require negative power to turn the segment LED on. i.e. power flow from common anode pin to segment pin to turn on the LED
Step 6: Port Manipulation Style Start From Zero
GPIO style code is simple and straight forward, but it only can manipulate 1 pin for each function call. The code will become too long and hard to read for further development. There are 2 methods to overcome this:
- refactor common code to function call
- use port manipulation
Port manipulation allow for lower-level and faster manipulation of the i/o pins of the microcontroller, let's try it first.
ATtiny861 have 2 port IO, port A and port B.
Firstly, use Data Direction Register, DDRA and DDRB, to set the IO pins to output: (0: input; 1: output)
void setup() {
// init GPIO
DDRA = B11111111; // set port A all pins as output
DDRB = B01000000; // set common anode pins as output
}
And then, use Data Register, PORTA and PORTB, to set the IO value: (0: LOW, 1: HIGH)
void loop() {
// display digit zero
PORTB = B01000000; // enable common anode
PORTA = B00011000; // set segment A, B, C, D, E, F on; G, H off
}
Note: the value begin with a letter 'B' and follow with 8 digits represent a 8 bit binary number, it has a few variation, e.g. the follow line all have the same effect:
DDRB = B00011000;
DDRB = 0b00011000;
DDRB = 0B00011000;
Step 7: GPIO Style VS Port Manipulation Style
Port Manipulation direct operate 8 pins at the same port instead of 8 pin operate functions, so:
- it reduce the code quantity, so it is easier to maintain when the logic growth
- it run faster (1 register operation instead of 8 Library function), you will find the different if adding more digit later on
- reduce the program size (836 bytes vs 330 bytes in previous example), it is very important while ATtiny861 microcontroller only have 8192 bytes to store the program
Port manipulation require bitwise calculation, it is a little bit difficult for a beginner but it has many advantage if you need to manipulate many pins at the same time, such as driving the seven segment display. And also we can define some constant to overcome the difficulties.
Step 8: Define Mapping Constant
For ease of code readability, first define all segment and common anode pins mapping constant:
#define SEG_A B11111101 // PA1
#define SEG_B B11111110 // PA0
#define SEG_C B11011111 // PA5
#define SEG_D B10111111 // PA6
#define SEG_E B01111111 // PA7
#define SEG_F B11111011 // PA2
#define SEG_G B11110111 // PA3
#define SEG_H B11101111 // PA4
#define DIGIT_1 B01000000 // PB6
#define DIGIT_2 B00100000 // PB5
#define DIGIT_3 B00010000 // PB4
#define DIGIT_4 B00001000 // PB3
#define ALL_HIGH B11111111
And then define the digit array: (it also called font constant for seven segment display)
<p>uint8_t digit[] = {<br> SEG_A & SEG_B & SEG_C & SEG_D & SEG_E & SEG_F, // 0
SEG_B & SEG_C, // 1
SEG_A & SEG_B & SEG_D & SEG_E & SEG_G, // 2
SEG_A & SEG_B & SEG_C & SEG_D & SEG_G, // 3
SEG_B & SEG_C & SEG_F & SEG_G, // 4
SEG_A & SEG_C & SEG_D & SEG_F & SEG_G, // 5
SEG_A & SEG_C & SEG_D & SEG_E & SEG_F & SEG_G, // 6
SEG_A & SEG_B & SEG_C, // 7
SEG_A & SEG_B & SEG_C & SEG_D & SEG_E & SEG_F & SEG_G, // 8
SEG_A & SEG_B & SEG_C & SEG_D & SEG_F & SEG_G // 9
};
Step 9: Show Digit With Constant
You can now use the constant to display the current second value:
void loop() {
PORTA = ALL_HIGH; // reset PORT A all pins to HIGH
PORTB = DIGIT_1; // switch to first digit
PORTA = digit[millis() / 1000 % 10]; // show 0-9
delay(1);
}
Step 10: Connect All 4 Digits
After one digit trial run, it's time to connect all 4 digits, here is the connection summary:
ATtiny861
Pin 1 MOSI -> ISP programmer MOSI
Pin 2 MISO -> ISP programmer MISO
Pin 3 SCK -> ISP programmer SCK
Pin 4 D6/PB3 -> 47 Ohm resistor -> 4th digit Seven Segment Display Common Anode Pin
Pin 5 VCC -> ISP programmer VCC
Pin 6 GND -> ISP programmer GND
Pin 7 D5/PB4 -> 47 Ohm resistor -> 3rd digit Seven Segment Display Common Anode Pin
Pin 8 D4/PB5 -> 47 Ohm resistor -> 2nd digit Seven Segment Display Common Anode Pin
Pin 9 D3/PB6 -> 47 Ohm resistor -> 1st digit Seven Segment Display Common Anode Pin
Pin 10 RESET -> ISP programmer RESET
Pin 11 D13/PA7 -> All 4 digits Seven Segment Display E Pin
Pin 12 D12/PA6 -> All 4 digits Seven Segment Display D Pin
Pin 13 D11/PA5 -> All 4 digits Seven Segment Display C Pin
Pin 14 D10/PA4 -> All 4 digits Seven Segment Display H Pin
Pin 17 D14/PA3 -> All 4 digits Seven Segment Display G Pin
Pin 18 D2/PA2 -> All 4 digits Seven Segment Display F Pin
Pin 19 D1/PA1 -> All 4 digits Seven Segment Display A Pin
Pin 20 D0/PA0 -> All 4 digits Seven Segment Display B Pin
Step 11: Download Source Code
Download the full source code at GitHub, compile and upload to the microcontroller:
Step 12: How Digit Shared Pins Work?
4 Seven Segment Digit share same 8 IO pins but can display different value, How does it works?
Actually 4 digits is not turned on at the same time, the LEDs in each digit is switching to turn on one by one. However, the switching process is fast enough (1 ms) so you think it is turned on altogether. This optical illusion is called Persistence of vision.
If you set a longer delay between switching logic, say 10 ms or 100 ms, you can visualise the switching process:
delay(100);
Step 13: Install Battery
After all program test, you can remove the ISP connection and install the battery holder and power switch to the breadboard.
Step 14: What's Next?
- add colon LED between minutes and seconds digits
- add a reset button for count up again
- add more digits
- add count down feature
- add buzzer alert count down finished
- add RTC chip to make it as a clock
Step 15: Update: Add Colon LED
I have updated the source code for blinking colon LED, here is the addition connection summary:
ATtiny861
Pin 1 MOSI (PB0) -> 47 Ohm resistor -> 2 LED +ve
2 LED -ve -> GND