уторак, 28. јануар 2020.

Yet another one day build of a digital clock

So, what is the difference this time? I have used TM1637 four-digit, seven-segment display, instead of driverlesss display. The difference is in the number of components. Instead of having twelve resistors and four transistors to drive the 7-segment display, now I have a single display with the built-in driver.

The code is on github.

Here is the schematics:
When you compare that to my previous 4-digit 7-segment build, you will notice the difference. The code is simpler, too:

const int CLK = 17; //Set the CLK pin connection to the display
const int DIO = 16; //Set the DIO pin connection to the display

TM1637Display display(CLK, DIO); //set up the 4-Digit Display.

void setup() {

  //set the display brightness  (0x0a is max)
  display.setBrightness(0x05); 

  ...

}

void loop() {
  
  display.showNumberDec(hour_1*1000 + hour_2*100 + minute_1*10 + minute_2);

  toggle = ~toggle;
  if (toggle)
  {
    display.showNumberDecEx(hour_1*1000 + hour_2*100 + minute_1*10 + minute_2, (0x80 >> 1), true);
  } 
    delay(500);
}

This is the clock:



петак, 24. јануар 2020.

Another One Day Build - Big Digital Clock

Welcome to another digital clock build. This time I have made a big digital clock for my friend. The clock was made using programmable LED strip, cut into small pieces which would act as segments in a big 7-segment digit.

The code is on github.

Programmable LED strip

This is a very nice thing - you can set any color to the individual LED bulb on the strip:

On the picture above you can see two segments, each having four LEDs. Each LED is programmable and can be set to any color of the 16 million colors available. I have purchased two meters of W2812B LED strip and cut it in segments having four LEDs. Then I have connected all segments with three wires and created digits:

Here you can see seven segments placed temporarily on the cardboard, for the testing purposes. Then I have connected the remaining three digits and two dots between hours and minutes:

I have recycled my previous code for the LED clock so I could now set and turn on/off individual digits of my new display. Here is the main loop:

void loop() {
  setDots(counter % 2);
  setDigit(0, hour_1);
  setDigit(1, hour_2);
  setDigit(2, minute_1);
  setDigit(3, minute_2);
  delay(1000);

  counter++;
  if (counter == 2)
  {
    counter = 0;
  }
}

The code above relies on getting the current time from the DS3231 RTC, separated into hour_1, hour_2, minute_1 and minute_2 variables. Then it is just matter of setting the corresponding segments on/off:

void setDigit(int digit, int value)
{
  int i, j;
  for (i = DIGIT_IDX_START[digit]; i < (DIGIT_IDX_START[digit] + 28); i++)
  {
    j = (i - DIGIT_IDX_START[digit]) / 4;                  // j is segment index
    if (DIGIT_MATRIX[value][j])
    {
       switch (state)
      {
        case RED:
          leds[i] = CRGB(255, 0, 0);
          break;
        case GREEN:
          leds[i] = CRGB(0, 255, 0);
          break;
        case BLUE:
          leds[i] = CRGB(0, 0, 255);
          break;
        case WHITE:
          leds[i] = CRGB(255, 255, 255);
          break;
        case RAINBOW:
          leds[i] = CRGB((i % 4 + 1) * 64, (i % 4 + 2) * 64, (i % 4 + 3)* 64);
          break;
        case BLACK:
          leds[i] = CRGB(0, 0, 0);
          break;
        default:
          leds[i] = CRGB(255, 0, 0);
      }
    }
    else
    {
      leds[i] = CRGB(0, 0, 0);
    }
  }
  FastLED.setBrightness(brightness);
  FastLED.show();
}

The FastLED library works by declaring an array of LEDS in the program. Here is my array of LEDs:

#define LED_PIN     2
#define NUM_LEDS    4*28

CRGB leds[NUM_LEDS + 2];

int DIGIT_IDX_START[] = {0, 28, 56, 84};
int DIGIT_MATRIX[][7] = {
// A     B     C     D     E     F     G  
{HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, LOW},   // 0
{LOW,  HIGH, HIGH, LOW,  LOW,  LOW,  LOW},   // 1
{HIGH, HIGH, LOW,  HIGH, HIGH, LOW,  HIGH},  // 2
{HIGH, HIGH, HIGH, HIGH, LOW,  LOW,  HIGH},  // 3
{LOW,  HIGH, HIGH, LOW,  LOW,  HIGH, HIGH},  // 4
{HIGH, LOW,  HIGH, HIGH, LOW,  HIGH, HIGH},  // 5
{HIGH, LOW,  HIGH, HIGH, HIGH, HIGH, HIGH},  // 6
{HIGH, HIGH, HIGH, LOW,  LOW,  LOW,  LOW},   // 7
{HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH},  // 8
{HIGH, HIGH, HIGH, HIGH, LOW,  HIGH, HIGH},  // 9
{LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  HIGH},  // '-'  index is 10
{LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW}    // BLANK index is 11
};

I have an array of 4*28 + 2 LEDS for 4 digits and two dots. The DIGIT_IDX_START array holds the starting indices of all four digits in that array of LEDS. 

The final look is below. I have placed a white plastic board over LEDs to disperse the light and now it is quite nice to watch in all lighting conditions:


The button at the lower right corner turns on/off the display.

петак, 10. јануар 2020.

One Day Build - Digital Clock

I have recently decided to make a digital table clock. The design I chose consists of three important parts:
- ESP32,
- 7-segment four digits display with no special driver, and
- DS3231 real time clock.

The code is on github.

Since ESP32 has WiFi module, I used it for two purposes: to obtain the correct time once a day (and synchronize the DS3231 to that correct time), and to obtain the temperature information from my weather server, so I could get the outside temperature with a single button press.

I purchased the most simple 7-segment display which has 12 pins: 8 pins for the digit segment selection and 4 pins for the digit selection. Those four pins are common cathodes of the LEDs making each digit - that is why it has four common cathodes - for those four digits.

DS3231 is a good RTC with temperature compensation and optional battery (CR2032) to work without external power source.

Here is the schematics:


Driving the 7-segment four digit display is quite interesting. You cannot illuminate more than one digit in time. This means that you need to illuminate the first digit, then the second, then the third, and the fourth, and then to do it all again, fast. Thanks to our vision persistence, we get the impression that all four digits are illuminated. 

If you look at the schematics, you will notice that the digit definition is done using GPIO pins 32, 33, 25, 26, 27, 14, 12, and 2, which turn on/off segments a, b, c, d, e, f, g, and dot. That would only set the corresponding LEDs input to high or low voltage, but would not illuminate them (the circuit would not be closed until the common cathode is connected to the ground). To choose which digit will be illuminated (to have the common cathode connected to the ground), I used GPIO pins 16, 17, 18, and 19. Logical one (3.3V) on those GPIO pins would turn on the corresponding transistor and it would connect the common cathode of the connected digit to the ground.

// GPIO ports for LEDs
//           Dot A   B   C   D   E   F   G
int LEDS[] = {2, 32, 33, 25, 26, 27, 14, 12};

// GPIO ports for digits
int DIGITS[] = { 18, 19, 17, 16 };

Next I defined the matrix of LEDs for digit representation:

int DIGIT_MATRIX[][7] = {
// A     B     C     D     E     F     G  
{HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, LOW},   // 0
{LOW,  HIGH, HIGH, LOW,  LOW,  LOW,  LOW},   // 1
{HIGH, HIGH, LOW,  HIGH, HIGH, LOW,  HIGH},  // 2
{HIGH, HIGH, HIGH, HIGH, LOW,  LOW,  HIGH},  // 3
{LOW,  HIGH, HIGH, LOW,  LOW,  HIGH, HIGH},  // 4
{HIGH, LOW,  HIGH, HIGH, LOW,  HIGH, HIGH},  // 5
{HIGH, LOW,  HIGH, HIGH, HIGH, HIGH, HIGH},  // 6
{HIGH, HIGH, HIGH, LOW,  LOW,  LOW,  LOW},   // 7
{HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH},  // 8
{HIGH, HIGH, HIGH, HIGH, LOW,  HIGH, HIGH},  // 9
{LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  HIGH},  // '-'  index is 10
{LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW}    // BLANK index is 11
};

To display a digit, you need to call two following functions:

void displayDigit(int value)
{
  int j, k;
  for (j = 1; j < 8; j++)
  {
    digitalWrite(LEDS[j], (DIGIT_MATRIX[value][j-1]));
  }
}

The function above will set the corresponding LEDs for the given digit, but will not illuminate anything, since we haven't selected which one of the four digits should be activated. To do so, we need the following function:

void activateDigit(int digit)
{
  switch(digit)
  {
    case 0:
      digitalWrite(DIGITS[0], HIGH);
      digitalWrite(DIGITS[1], LOW);
      digitalWrite(DIGITS[2], LOW);
      digitalWrite(DIGITS[3], LOW);
      digitalWrite(LEDS[0], LOW);
    break;
    case 1:
      digitalWrite(DIGITS[0], LOW);
      digitalWrite(DIGITS[1], HIGH);
      digitalWrite(DIGITS[2], LOW);
      digitalWrite(DIGITS[3], LOW);
      digitalWrite(LEDS[0], LOW);
    break;
    case 2:
      digitalWrite(DIGITS[0], LOW);
      digitalWrite(DIGITS[1], LOW);
      digitalWrite(DIGITS[2], HIGH);
      digitalWrite(DIGITS[3], LOW);
      digitalWrite(LEDS[0], LOW);
    break;
    case 3:
      digitalWrite(DIGITS[0], LOW);
      digitalWrite(DIGITS[1], LOW);
      digitalWrite(DIGITS[2], LOW);
      digitalWrite(DIGITS[3], HIGH);
      digitalWrite(LEDS[0], LOW);
    break;
    default:
      digitalWrite(DIGITS[0], LOW);
      digitalWrite(DIGITS[1], LOW);
      digitalWrite(DIGITS[2], LOW);
      digitalWrite(DIGITS[3], LOW);
      digitalWrite(LEDS[0], LOW);
  }
}

So, displaying time from the RTC module looks like this. First we read the time from the RTC (once in two seconds):

void read_time() 
{
  DateTime now = rtc.now();
  int hour = now.hour();
  int minute = now.minute();

  hour_1 = hour / 10;
  hour_2 = hour % 10;
  minute_1 = minute / 10;
  minute_2 = minute % 10;
}

We break up the time into four digits: hour_1, hour_2, minute_1, and minute_2. Then, in the main loop, we display that time by showing each digit quickly (each digit is illuminated one millisecond):

void loop()
{
  switch(counter)
  {
    case 0:
      displayDigit(hour_1);
      break;
    case 1:
      displayDigit(hour_2);
      break;
    case 2:
      displayDigit(minute_1);
      break;
    case 3:
      displayDigit(minute_2);
      break;
  }
  activateDigit(counter);
  counter++;
  if (counter == 4)
  {
    counter = 0;
  }

  // duty cycle adjustment for the LED brightness
  delayMicroseconds(500);
  activateDigit(-1);
  delayMicroseconds(500);
}

As you can see, the time is fetched from the RTC outside of the main loop. I have done that because obtaining the time from the RTC inside the loop might screw up the LED illumination, since the main loop would be blocked while reading the time from the RTC. To avoid that, I have used the ESP32 task to do the RTC reading. In the setup function, I have created a task for obtaining time:

// create a task that will be executed in the Task1code() function,
// with priority 1 and executed on core 0
// this task will get time from RTC every two seconds
  xTaskCreatePinnedToCore(
              Task1code,   /* Task function. */
              "Task1",     /* name of task. */
              10000,       /* Stack size of task */
              NULL,        /* parameter of the task */
              1,           /* priority of the task */
              &Task1,      /* Task handle */
              1);          /* pin task to core 1 */


Task1code() is a task function which is executed in parallel with the loop() function. Task1 is a task handler variable, which holds a handler for this task:

TaskHandle_t Task1;

void Task1code( void * pvParameters ){
  for(;;){
     read_time();
     delay(2000);
  } 
}

This task is executed as an infinite loop. It obtains the time from the RTC and then sleeps for two seconds. Then it does all of it again.

This is the time displayed by the clock:



Getting the outside temperature from my weather server was an interesting task. When I press the button, the clock would have to connect to the weather server, send the HTTP request for the temperature info, receive the HTTP response, parse it and display the outside temperature on the clock, instead of current time.

const char root_ca[]= {
// here comes the certificate in bytes...
};

http.begin("https://myserver/status", root_ca);

int httpCode = http.GET();

// httpCode will be negative on error
if(httpCode > 0) {
    // got the response from the server
    if(httpCode == HTTP_CODE_OK) 
    {
        String payload = http.getString();
        String t = parse_response(payload);
        int temp = t.toInt();
        if (t.startsWith("-"))
        {
          hour_2 = 10;  // "-" sign
          temp  = -temp;
        } else 
        {
          hour_2 = 11;  // BLANK character
        }
        hour_1 = 11;
        minute_1 = temp / 10;
        minute_2 = temp % 10;
    }
} else {
    Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}

http.end();

The clock would display the temperature outside:



When I press the button again, the clock will display the internal temperature, obtained from the RTC. RTC has the temperature sensor built in so that it can compensate the time drift caused by the temperature change. That sensor info is exposed to the programmer:

float int_temp = rtc.getTemperature();
int temp = (int)int_temp;
temp = temp -2;
hour_1 = 11;
hour_2 = 11;
minute_1 = temp / 10;
minute_2 = temp % 10;

Here is the temperature inside:


How is this clock initialized when powered up? How is this clock synchronized to the precise time? I used the WiFi connection to connect the clock to the time server:

http.begin("https://myserver/time", root_ca);

int httpCode = http.GET();

// httpCode will be negative on error
if(httpCode > 0) {
    // got the response from the server
    if(httpCode == HTTP_CODE_OK) 
    {
        String payload = http.getString();
        parse_time(payload);
        rtc.adjust(DateTime(year, month, day, hour, minute, second));
    }
} else {
    Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}

http.end();

The code above does not synchronize the clock to the exactly precise time (because of the delay), but it is good enough for me.

Conclusion

This table clock build was very interesting. The main challenge was to make all the digits show properly on the 7-segment display. When I mastered that, the rest was a breeze: getting the temperature from the weather server and showing the internal room temperature.