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 I 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 suggests 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);
}
}
}