Cheap 7 segment & DS1307 clock project

Hi all. I’m attempting to make a simple clock on a budget. I have a 4 digital 7 segment display 7-Segment Display - 4-Digit (Red) - COM-09483 - SparkFun Electronics and a DS1307 module.

I would like to simply display the hours and minutes, so I have got the following code ready for that…

// Date and time functions using a DS1307 RTC connected via I2C and Wire lib

#include <Wire.h>
#include "RTClib.h"

RTC_DS1307 rtc;

void setup () {
  Serial.begin(57600);
#ifdef AVR
  Wire.begin();
#else
  Wire1.begin(); // Shield I2C pins connect to alt I2C bus on Arduino Due
#endif
  rtc.begin();

  if (! rtc.isrunning()) {
    Serial.println("RTC is NOT running!");
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(__DATE__, __TIME__));
  }
}
void loop () {
    DateTime now = rtc.now();
    Serial.print(' ');
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.println();
    delay(1000);
}

I have got the 7 segment display working from some code I found from sparkfun, which counts and displays the seconds using millis(). It uses PWM on the digit pins so I’m not using resistors which is handy.

The idea that I had was to convert the hours into a string, then add the minutes to the string, then update the display with that string - I’ve had no success in doing so. Any guidance would be gratefully received.

/*
 6-13-2011
 Spark Fun Electronics 2011
 Nathan Seidle
 
 This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
 
 4 digit 7 segment display:
 http://www.sparkfun.com/products/9483
 Datasheet: 
 http://www.sparkfun.com/datasheets/Components/LED/7-Segment/YSD-439AR6B-35.pdf
 
 This is an example of how to drive a 7 segment LED display from an ATmega without the use of current limiting resistors.
 This technique is very common but requires some knowledge of electronics - you do run the risk of dumping too 
 much current through the segments and burning out parts of the display. If you use the stock code you should be ok, but 
 be careful editing the brightness values.
 
 This code should work with all colors (red, blue, yellow, green) but the brightness will vary from one color to the next
 because the forward voltage drop of each color is different. This code was written and calibrated for the red color.
 
 This code will work with most Arduinos but you may want to re-route some of the pins.
 
 7 segments
 4 digits
 1 colon
 =
 12 pins required for full control 
 
 */

int digit1 = 11; //PWM Display pin 1
int digit2 = 10; //PWM Display pin 2
int digit3 = 9; //PWM Display pin 6
int digit4 = 6; //PWM Display pin 8

//Pin mapping from Arduino to the ATmega DIP28 if you need it
//http://www.arduino.cc/en/Hacking/PinMapping
int segA = A1; //Display pin 14
int segB = 3; //Display pin 16
int segC = 4; //Display pin 13
int segD = 5; //Display pin 3
int segE = A0; //Display pin 5
int segF = 7; //Display pin 11
int segG = 8; //Display pin 15

int col = 13; 

void setup() {                
  pinMode(segA, OUTPUT);
  pinMode(segB, OUTPUT);
  pinMode(segC, OUTPUT);
  pinMode(segD, OUTPUT);
  pinMode(segE, OUTPUT);
  pinMode(segF, OUTPUT);
  pinMode(segG, OUTPUT);

  pinMode(digit1, OUTPUT);
  pinMode(digit2, OUTPUT);
  pinMode(digit3, OUTPUT);
  pinMode(digit4, OUTPUT);
  pinMode(col, OUTPUT);

  pinMode(col, OUTPUT);
  digitalWrite(col, LOW);
}

void loop() {

  //long startTime = millis();

  displayNumber(millis()/1000);

  //while( (millis() - startTime) < 2000) {
  //displayNumber(1217);
  //}
  //delay(1000);  
}

//Given a number, we display 10:22
//After running through the 4 numbers, the display is left turned off

//Display brightness
//Each digit is on for a certain amount of microseconds
//Then it is off until we have reached a total of 20ms for the function call
//Let's assume each digit is on for 1000us
//If each digit is on for 1ms, there are 4 digits, so the display is off for 16ms.
//That's a ratio of 1ms to 16ms or 6.25% on time (PWM).
//Let's define a variable called brightness that varies from:
//5000 blindingly bright (15.7mA current draw per digit)
//2000 shockingly bright (11.4mA current draw per digit)
//1000 pretty bright (5.9mA)
//500 normal (3mA)
//200 dim but readable (1.4mA)
//50 dim but readable (0.56mA)
//5 dim but readable (0.31mA)
//1 dim but readable in dark (0.28mA)

void displayNumber(int toDisplay) {
#define DISPLAY_BRIGHTNESS  50

#define DIGIT_ON  HIGH
#define DIGIT_OFF  LOW

  long beginTime = millis();

  for(int digit = 4 ; digit > 0 ; digit--) {

    //Turn on a digit for a short amount of time
    switch(digit) {
    case 1:
      digitalWrite(digit1, DIGIT_ON);
      break;
    case 2:
      digitalWrite(digit2, DIGIT_ON);
      break;
    case 3:
      digitalWrite(digit3, DIGIT_ON);
      break;
    case 4:
      digitalWrite(digit4, DIGIT_ON);
      break;
    }

    //Turn on the right segments for this digit
    lightNumber(toDisplay % 10);
    toDisplay /= 10;
    //toDisplay = 9;
    digitalWrite(col, HIGH); //turn on colon
    delayMicroseconds(DISPLAY_BRIGHTNESS); //Display this digit for a fraction of a second (between 1us and 5000us, 500 is pretty good)
    digitalWrite(col, LOW); //turn off colon
    //Turn off all segments
    lightNumber(10); 

    //Turn off all digits
    digitalWrite(digit1, DIGIT_OFF);
    digitalWrite(digit2, DIGIT_OFF);
    digitalWrite(digit3, DIGIT_OFF);
    digitalWrite(digit4, DIGIT_OFF);
  }

  while( (millis() - beginTime) < 10) ; //Wait for 20ms to pass before we paint the display again
}

//Given a number, turns on those segments
//If number == 10, then turn off number
void lightNumber(int numberToDisplay) {

#define SEGMENT_ON  LOW
#define SEGMENT_OFF HIGH

  switch (numberToDisplay){

  case 0:
    digitalWrite(segA, SEGMENT_ON);
    digitalWrite(segB, SEGMENT_ON);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_ON);
    digitalWrite(segE, SEGMENT_ON);
    digitalWrite(segF, SEGMENT_ON);
    digitalWrite(segG, SEGMENT_OFF);
    break;

  case 1:
    digitalWrite(segA, SEGMENT_OFF);
    digitalWrite(segB, SEGMENT_ON);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_OFF);
    digitalWrite(segE, SEGMENT_OFF);
    digitalWrite(segF, SEGMENT_OFF);
    digitalWrite(segG, SEGMENT_OFF);
    break;

  case 2:
    digitalWrite(segA, SEGMENT_ON);
    digitalWrite(segB, SEGMENT_ON);
    digitalWrite(segC, SEGMENT_OFF);
    digitalWrite(segD, SEGMENT_ON);
    digitalWrite(segE, SEGMENT_ON);
    digitalWrite(segF, SEGMENT_OFF);
    digitalWrite(segG, SEGMENT_ON);
    break;

  case 3:
    digitalWrite(segA, SEGMENT_ON);
    digitalWrite(segB, SEGMENT_ON);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_ON);
    digitalWrite(segE, SEGMENT_OFF);
    digitalWrite(segF, SEGMENT_OFF);
    digitalWrite(segG, SEGMENT_ON);
    break;

  case 4:
    digitalWrite(segA, SEGMENT_OFF);
    digitalWrite(segB, SEGMENT_ON);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_OFF);
    digitalWrite(segE, SEGMENT_OFF);
    digitalWrite(segF, SEGMENT_ON);
    digitalWrite(segG, SEGMENT_ON);
    break;

  case 5:
    digitalWrite(segA, SEGMENT_ON);
    digitalWrite(segB, SEGMENT_OFF);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_ON);
    digitalWrite(segE, SEGMENT_OFF);
    digitalWrite(segF, SEGMENT_ON);
    digitalWrite(segG, SEGMENT_ON);
    break;

  case 6:
    digitalWrite(segA, SEGMENT_ON);
    digitalWrite(segB, SEGMENT_OFF);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_ON);
    digitalWrite(segE, SEGMENT_ON);
    digitalWrite(segF, SEGMENT_ON);
    digitalWrite(segG, SEGMENT_ON);
    break;

  case 7:
    digitalWrite(segA, SEGMENT_ON);
    digitalWrite(segB, SEGMENT_ON);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_OFF);
    digitalWrite(segE, SEGMENT_OFF);
    digitalWrite(segF, SEGMENT_OFF);
    digitalWrite(segG, SEGMENT_OFF);
    break;

  case 8:
    digitalWrite(segA, SEGMENT_ON);
    digitalWrite(segB, SEGMENT_ON);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_ON);
    digitalWrite(segE, SEGMENT_ON);
    digitalWrite(segF, SEGMENT_ON);
    digitalWrite(segG, SEGMENT_ON);
    break;

  case 9:
    digitalWrite(segA, SEGMENT_ON);
    digitalWrite(segB, SEGMENT_ON);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_ON);
    digitalWrite(segE, SEGMENT_OFF);
    digitalWrite(segF, SEGMENT_ON);
    digitalWrite(segG, SEGMENT_ON);
    break;

  case 10:
    digitalWrite(segA, SEGMENT_OFF);
    digitalWrite(segB, SEGMENT_OFF);
    digitalWrite(segC, SEGMENT_OFF);
    digitalWrite(segD, SEGMENT_OFF);
    digitalWrite(segE, SEGMENT_OFF);
    digitalWrite(segF, SEGMENT_OFF);
    digitalWrite(segG, SEGMENT_OFF);
    break;
  }
}

What on earth makes you think that PWM is connected in some fashion with current limiting? :astonished:

Of course you need resistors in each segment lead.

Paul__B: What on earth makes you think that PWM is connected in some fashion with current limiting? :astonished:

Absolutely nothing.

Taken from the header on the example I'm using..

This is an example of how to drive a 7 segment LED display from an ATmega without the use of current limiting resistors. This technique is very common but requires some knowledge of electronics - you do run the risk of dumping too much current through the segments and burning out parts of the display. If you use the stock code you should be ok, but be careful editing the brightness values.

Any advise on the task of interfacing the code I'm using to read the time with the 7 segment example?

Thanks

dtokez: Taken from the header on the example I'm using..

I think we would like to know where you found that code - I can't see the link to it on the page you cite.

The notion that it is OK if you overload the Arduino only part of the time is not likely to be seen too favorably by the people here.

What is really a concern also, is if for whatever reason, the code crashes while it is feeding excess current to a segment or group thereof.

Just commenting between cases here; haven't had a chance to look at the code as such ...

Paul__B: What on earth makes you think that PWM is connected in some fashion with current limiting? :astonished:

Of course you need resistors in each segment lead.

Oh? Look at the diagram for this board: https://www.sparkfun.com/products/10956

Or this one: https://www.sparkfun.com/products/10930

Both of them have a 4 digit 7 segment display connected DIRECTLY to the 328P with no resistors.

The segments are pulsed on and off to control brightness.

PWM'ing LED's for pseudo current limiting is a common technique.

And it's not going to hurt the output pins of the AVR. They are mosfets. They act as resistors anyway. What damages a pin is heat caused by a sustained short circuit or sustained overcurrent.

Pulsing an output directly to an LED will not hurt the port or the LED.

Oh and @dtokez: You may be interested in one or the other of the Sparkfun boards I listed above. They both use a 328P chip and the LED display you have. Although they are kits for a specific purpose, there's no reason why you can't write your own code and do something else with them.

In fact, I have a multimeter kit which I use solely as a carrier board for the 328P and LED display. I've got code in it to make it run as a multi-function display for a DC power supply I built.

It's a handy, pre-built board and you can make it do anything you like simply be changing the code.

Paul__B:

dtokez: Taken from the header on the example I'm using..

I think we would like to know where you found that code - I can't see the link to it on the page you cite.

The notion that it is OK if you overload the Arduino only part of the time is not likely to be seen too favorably by the people here.

What is really a concern also, is if for whatever reason, the code crashes while it is feeding excess current to a segment or group thereof.

Just commenting between cases here; haven't had a chance to look at the code as such ...

As stated in the OP the code is from Sparkfun. I'm aware that it is not the most foolproof way to connect LED's but I have never had a 328p crash on me yet.

@Krupski - thanks for the info! Those boards look interesting. I was hoping to check out the code because that is the part that I'm stuck on, I've got the 7 segment running nicely I just need to work out how to get it to display my time values :~

Check this out Alarm clock from Atmega328 and 7-segment display by Nick Gammon

dtokez:
@Krupski - thanks for the info! Those boards look interesting. I was hoping to check out the code because that is the part that I’m stuck on, I’ve got the 7 segment running nicely I just need to work out how to get it to display my time values :~

Grab this code… it’s written for the Sparkfun Multimeter kit board. It’s not multimeter code though. It’s got functions to do the LED display, read the pushbutton (with debounce) and analog read of the 3 inputs.

You should be able to make whatever you want by cut-n-pasting pieces of this code into your own program.

Hope it helps (and hope it helps you understand how the LED board works).

///////////////////////////////////////////////////////////////////////////////////
// demo code - all the stuff you need to use the
// Sparkfun Multimeter Kit board for other purposes.
// https://www.sparkfun.com/products/10956
// (c) October 2013 Roger A. Krupski <rakrupski@verizon.net>
// This program is free, open source, do whatever you like with it.
///////////////////////////////////////////////////////////////////////////////////

// PORT B (digits)
#define DIG_0 _BV(0)
#define DIG_1 _BV(1)
#define DIG_2 _BV(2)
#define DIG_3 _BV(3)
#define DIG_C _BV(4) // c=colon
#define BUTTON _BV(5) // button

// PORTC (analog in, piezo beeper)
#define VSENSE _BV(0) // the voltage sense goes here
#define CSENSE _BV(1) // the current sense goes here
#define RSENSE _BV(2) // the resistor sense goes here
#define BUZZ_1 _BV(3) // these two pins are...
#define BUZZ_2 _BV(4) // connected to the piezo beeper.

// PORT D (segments)
#define SEG_A  _BV(0) // LED segments and decimal point
#define SEG_B  _BV(1)
#define SEG_C  _BV(2)
#define SEG_D  _BV(3)
#define SEG_E  _BV(4)
#define SEG_F  _BV(5)
#define SEG_G  _BV(6)
#define SEG_DP _BV(7)

// seven segment bit patterns
#define SEGS_0 ~(SEG_A|SEG_B|SEG_C|SEG_D|SEG_E|SEG_F)
#define SEGS_1 ~(SEG_B|SEG_C)
#define SEGS_2 ~(SEG_A|SEG_B|SEG_D|SEG_E|SEG_G)
#define SEGS_3 ~(SEG_A|SEG_B|SEG_C|SEG_D|SEG_G)
#define SEGS_4 ~(SEG_B|SEG_C|SEG_F|SEG_G)
#define SEGS_5 ~(SEG_A|SEG_C|SEG_D|SEG_F|SEG_G)
#define SEGS_6 ~(SEG_A|SEG_C|SEG_D|SEG_E|SEG_F|SEG_G)
#define SEGS_7 ~(SEG_A|SEG_B|SEG_C)
#define SEGS_8 ~(SEG_A|SEG_B|SEG_C|SEG_D|SEG_E|SEG_F|SEG_G)
#define SEGS_9 ~(SEG_A|SEG_B|SEG_C|SEG_D|SEG_F|SEG_G)
#define SEGS_OFF (SEG_A|SEG_B|SEG_C|SEG_D|SEG_E|SEG_F|SEG_G|SEG_DP)

#define brite 20 // arbitrary value: larger=brighter

uint16_t value; // display value must be global so ISR can see it

void setup (void)
{
	PORTB = 0 | BUTTON; // set the "button" pin high (same as pinMode INPUT_PULLUP)
	PORTC = 0b00000000; // clear all PORTB bits (digit drivers)
	PORTD = 0b11111111; // all PORTD bits high (all segments off)
	DDRB  = 0b00011111; // DDR 4 digits + decimal point as outputs
	DDRC  = 0b00011000; // buzzer lines output, all ADC input
	DDRD  = 0b11111111; // segment drivers all outputs

	value = 0; // init display value to 0

	// setup timer 1
	TCCR1A = 0b00000000; // timer control register a "normal port operation"
	TCCR1B = (_BV(WGM12) | _BV(CS12) | _BV(CS10));	// CTC mode, F/1024
	TCCR1C = 0b00000000; // do not force output compare
	OCR1A = 0x50; // load compare register (sets display update rate)
	TIMSK1 = _BV(OCIE1A); // enable the ISR
}

void loop (void)
{
	if (getButton ()) { // if button pressed
		value += 1; // increment value
		beep (); // beep
                // yeah, that's all it does.
	}
}

// we keep each number on for "N" * brite where "N" is the number
// of segments lit so that each number is the same brightness.
void display (uint8_t digit, uint8_t number)
{
	PORTB |= _BV(digit); // turn on the correct digit

	switch (number) {
		case -1:
			PORTD = SEGS_OFF;
			break;
		case 0:
			PORTD = SEGS_0;
			_delay_us (6 * brite);
			break;
		case 1:
			PORTD = SEGS_1;
			_delay_us (2 * brite);
			break;
		case 2:
			PORTD = SEGS_2;
			_delay_us (5 * brite);
			break;
		case 3:
			PORTD = SEGS_3;
			_delay_us (5 * brite);
			break;
		case 4:
			PORTD = SEGS_4;
			_delay_us (4 * brite);
			break;
		case 5:
			PORTD = SEGS_5;
			_delay_us (5 * brite);
			break;
		case 6:
			PORTD = SEGS_6;
			_delay_us (6 * brite);
			break;
		case 7:
			PORTD = SEGS_7;
			_delay_us (3 * brite);
			break;
		case 8:
			PORTD = SEGS_8;
			_delay_us (7 * brite);
			break;
		case 9:
			PORTD = SEGS_9;
			_delay_us (6 * brite);
			break;
		default:
			break;
	}

	PORTB &= ~_BV(digit); // turn on the correct digit
	PORTD = SEGS_OFF;
}

// ISR on timer 1
ISR(TIMER1_COMPA_vect)
{
	uint16_t v = value;
	uint8_t th = v / 1000; v -= th * 1000;
	uint8_t hu = v / 100; v -= hu * 100;
	uint8_t tn = v / 10; v -= tn * 10;
	uint8_t on = v / 1; v -= on * 1;
	display (0, th); display (0, -1);
	display (1, hu); display (1, -1);
	display (2, tn); display (2, -1);
	display (3, on); display (3, -1);
}

uint16_t readAnalog (uint8_t channel, uint16_t avg)
{
	uint16_t x = avg;
	uint32_t accum = 0;

	// ADC enable, auto trigger, clk prescale F/8 (nice clean conversions)
	ADCSRA = _BV(ADEN) | _BV(ADATE) | _BV(ADPS1) | _BV(ADPS0);

	// input is on ADC0, 1.1v internal reference (use ONE or the OTHER line)
	ADMUX = channel | _BV(REFS0) | _BV(REFS1); // if AREF is isolated, use this
        ADMUX = channel; // if AREF is not isolated (Sparkfun default), use this

	// start an ADC converter
	ADCSRA |= _BV(ADSC);

	// run ADC in auto-trigger mode, acquire "avg" number of samples
	while (x--) {
		while( !(ADCSRA & _BV(ADIF))); // ADIF goes high when a conversion it done
		accum += ((ADCL) | ((ADCH) << 8)); // add analog value to accumulator
	}

	ADCSRA = 0;	// all done, shut off the ADC
	return (uint16_t)(accum / avg); // return averaged reading
}

void beep (void)
{
	uint16_t n;
	cli (); // see note below
	for (n = 0; n < 250; n++) {
		PORTC |= BUZZ_1; // see-saw piezo pins back and forth
		PORTC &= ~BUZZ_2;
		_delay_us (500); // delay to make a tone
		PORTC |= BUZZ_2; // see-saw piezo pins back and forth
		PORTC &= ~BUZZ_1;
		_delay_us (500); // delay to make a tone
	}
	sei (); // see note below
	PORTC &= ~(BUZZ_1 | BUZZ_2); // both piezo pins off

	// NOTE: The "cli()" function turns off interrupts and makes the beep
	// sound nice and clean, but it interrupts the updating of the LED
	// display. If "cli()" and "sei()" are commented out, the beep will
	// not disturb the LED display, but it will sound raspy. You decide.
}

uint8_t getButton (void)
{
	uint16_t debounce = 1000; // arbitrary time period
	if (PINB & BUTTON) {
		return 0; // button isn't pressed, exit
	}
	while (debounce--) {
		if (PINB & BUTTON) {
			debounce = 1000; // button bounced, reset timer
		}
	}
	while (!(PINB & BUTTON)); // hang here until the button is released
	return 1; // return "we got a button"
}

oops forgot to mention: the analog reference pin is tied to VCC on the Sparkfun board. To use the programmable references, you need to plug the 328P chip into the board with pin 21 bent slightly outward so that it hangs outside the socket instead of being inserted into the socket (you ARE going to use a socket when you build the board I hope?)

Thanks all for the help and sharing the code. I have worked it out now, it was extremely simple lol. I just have to insert a value into the displayNumber function :)

displayNumber(millis()/1000);

now to try and work out how to create a string from the hour and minutes to display