c++ – Clock display controlled by esp32

I recently got fascinated by the world of micro controllers, and chose to do a simple clock project to see what is what. First attempt I used .net nanoframework since I’m a C# coder by day, but I haven’t been able to figure out how to make it execute fast enough.

I ported my code to C++ and used PlatformIO to build and deploy. It works and completely eliminated the flickering problem I had, but as I haven’t used C for over a decade and never really sunk my teeth into C++, I could really use some input on best practices and similar.

As my project grows I’ll need to put my classes into separate .hpp files (google suggest C++ continues C’s tradition of putting the interface in a header file and the implementation in a .cpp file — I am a little bit miffed VSCode doesn’t offer me this simple refactoring, so I feel I’m missing something obvious). But apart from that, what else am I missing?

Enums felt a bit off to me. In C# I’d do a myEnum++. Porting foreach caused me some headache. I found std::for_each() but in the end I decided for’s syntax was actually more readable. My std::array initialization is probably also a bit wonky.

To run this in its full glory: an ESP32 controller is required, a TI CD74HC4511E BCD decoder and a LiteOn LTC-2723Y 4 digit 7 segment display. Probably good to include seven resistors for good measure.

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"
#include "driver/gpio.h"
#include <array>
#include <algorithm>

extern "C"
{
  void app_main(void);
}

// Outputs a 4-bit value to a BCD decoder
class BCDWriter
{
public:
  BCDWriter(gpio_num_t d0, gpio_num_t d1, gpio_num_t d2, gpio_num_t d3)
  {
    _bits = {d0, d1, d2, d3};
    for (auto pin = _bits.begin(); pin != _bits.end(); pin++)
    {
      gpio_pad_select_gpio(*pin);
      gpio_set_direction(*pin, GPIO_MODE_OUTPUT);
    }
  }

  void Write(short value)
  {
    for (auto pin = _bits.begin(); pin != _bits.end(); pin++)
    {
      gpio_set_level(*pin, (value & 1) > 0 ? 1 : 0);
      value >>= 1;
    }
  }

private:
  std::array<gpio_num_t, 4> _bits;
};

// Controls a four digit 7 segment display (e.g. LiteOn LTC-2723Y)
// Assumes common cathode (set digit pin low to activate that digit's display)
class QuadDigitDisplay
{
public:
  enum QuadDigit
  {
    First,
    Second,
    Third,
    Fourth,
    Indicators
  };

  QuadDigitDisplay(gpio_num_t cc1, gpio_num_t cc2, gpio_num_t cc3, gpio_num_t cc4, gpio_num_t l)
  {
    _pins = {cc1, cc2, cc3, cc4, l};
    for (auto pin = _pins.begin(); pin != _pins.end(); pin++)
    {
      gpio_pad_select_gpio(*pin);
      gpio_set_direction(*pin, GPIO_MODE_OUTPUT);
    };
  }

  void SetHigh(QuadDigit quadDigit)
  {
    gpio_set_level(_pins(quadDigit), 1);
  }

  void SetLow(QuadDigit quadDigit)
  {
    gpio_set_level(_pins(quadDigit), 0);
  }

private:
  std::array<gpio_num_t, 5> _pins;
};

// Display "12:34" test output on the display
void app_main()
{
  printf("Hello PlatformIO!n");
  QuadDigitDisplay quadDisp(GPIO_NUM_26, GPIO_NUM_22, GPIO_NUM_18, GPIO_NUM_27, GPIO_NUM_19);
  BCDWriter bcd(GPIO_NUM_0, GPIO_NUM_17, GPIO_NUM_2, GPIO_NUM_5);

  QuadDigitDisplay::QuadDigit quadDigit = QuadDigitDisplay::QuadDigit::First;
  while (true)
  {
    uint8_t number = quadDigit == QuadDigitDisplay::QuadDigit::Indicators ? (uint8_t)2 : (uint8_t)quadDigit;
    bcd.Write(number);
    quadDisp.SetLow(quadDigit);
    vTaskDelay(5 / portTICK_PERIOD_MS);
    quadDisp.SetHigh(quadDigit);

    if (quadDigit == QuadDigitDisplay::QuadDigit::Indicators)
    {
      quadDigit = QuadDigitDisplay::QuadDigit::First;
    }
    else
    {
      quadDigit = QuadDigitDisplay::QuadDigit(quadDigit + 1);
    }
  }
}
```