diff --git a/firmware/components/skullc/skullc-peripherals b/firmware/components/skullc/skullc-peripherals index 470ad75..af9db5a 160000 --- a/firmware/components/skullc/skullc-peripherals +++ b/firmware/components/skullc/skullc-peripherals @@ -1 +1 @@ -Subproject commit 470ad7537695229e57f2332fdd54a96fed3e556a +Subproject commit af9db5a1b013938dc378c092ed1e12d0266348b9 diff --git a/firmware/main/CMakeLists.txt b/firmware/main/CMakeLists.txt index 66a7c4e..72e831c 100644 --- a/firmware/main/CMakeLists.txt +++ b/firmware/main/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( - SRCS "main.cpp" "wifi_provisioner.cpp" "clock_core.cpp" + SRCS "main.cpp" "wifi_provisioner.cpp" "clock_core.cpp" "vfd_driver.cpp" INCLUDE_DIRS "" REQUIRES nvs_flash esp_wifi esp_http_server etlcpp json skullc EMBED_FILES "${CMAKE_CURRENT_LIST_DIR}/static/index.html" diff --git a/firmware/main/clock_core.cpp b/firmware/main/clock_core.cpp index 570283a..b4c499e 100644 --- a/firmware/main/clock_core.cpp +++ b/firmware/main/clock_core.cpp @@ -7,18 +7,20 @@ #include "esp_expected.hpp" #include "clock_core_event.hpp" #include "string_helpers.hpp" +#include "vfd_driver.hpp" #include #include #include +#include +#include #include #include #include #include #include - -#include +#include namespace { @@ -28,6 +30,7 @@ const char* TAG = "clock_core"; struct Context { clock_core::EventGroup* events = nullptr; + VfdDriver* vfd = nullptr; Context(const Context&) = delete; Context(Context&&) = delete; @@ -141,22 +144,61 @@ std::expected wifiInitialize(const char* wifi_ssid, const char* return {}; } -etl::string<6> getCurrentTimeString() +etl::string<9> getCurrentTimeString() { std::time_t time_now; std::tm time_info{}; std::time(&time_now); localtime_r(&time_now, &time_info); - etl::string<6> buffer; + etl::string<9> buffer; wrapUnsafeStringOps(buffer, [&time_info](char* string, const int available) { - std::strftime(string, available, "%H:%M", &time_info); + std::strftime(string, available, " %H.%M ", &time_info); }); return buffer; } +bool IRAM_ATTR vfdUpdateEventHandler(gptimer_handle_t, const gptimer_alarm_event_data_t*, void* user_ctx) +{ + auto context = static_cast(user_ctx); + + BaseType_t task_awoken = pdFALSE; + xEventGroupSetBitsFromISR(context->events->rtos_event_group, clock_core::EventGroup::DISPLAY_UPDATE, &task_awoken); + + return task_awoken == pdTRUE; +} + +void initAndStartVfdTimer(Context* ctx) +{ + std::uint32_t clock_source_hz; + esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_APB, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &clock_source_hz); + + auto config = Utility::zeroInitialized(); + config.clk_src = GPTIMER_CLK_SRC_DEFAULT; + config.direction = GPTIMER_COUNT_UP; + config.resolution_hz = 1'000'000; + + gptimer_handle_t timer = nullptr; + gptimer_new_timer(&config, &timer); + + gptimer_event_callbacks_t callbacks = { + .on_alarm = vfdUpdateEventHandler + }; + gptimer_register_event_callbacks(timer, &callbacks, ctx); + + gptimer_enable(timer); + + auto alarm_config = Utility::zeroInitialized(); + alarm_config.alarm_count = 10'000; + alarm_config.reload_count = 0; + alarm_config.flags.auto_reload_on_alarm = true; + gptimer_set_alarm_action(timer, &alarm_config); + + gptimer_start(timer); +} + } namespace clock_core @@ -164,8 +206,11 @@ namespace clock_core void run(const char* wifi_ssid, const char* wifi_password) { + ESP_ERROR_CHECK(VfdDriver::defaultInitializeBus(SPI2_HOST, GPIO_NUM_6, GPIO_NUM_7, GPIO_NUM_5)); + EventGroup events; Context::getContext()->events = &events; + Context::getContext()->vfd = new VfdDriver(SPI2_HOST, CREATE_GPIO(GPIO_NUM_9), CREATE_GPIO(GPIO_NUM_10)); ESP_LOGI(TAG, "INITIAL: Events: %p, context: %p", &events, Context::getContext()); auto clock_timer = xTimerCreate("clock_core", @@ -196,6 +241,7 @@ void run(const char* wifi_ssid, const char* wifi_password) } xTimerStart(clock_timer, pdMS_TO_TICKS(10)); + initAndStartVfdTimer(Context::getContext()); } else if (bits & EventGroup::WIFI_FAILED) { @@ -208,7 +254,12 @@ void run(const char* wifi_ssid, const char* wifi_password) else if (bits & EventGroup::CLOCK_UPDATE) { const auto time = getCurrentTimeString(); - ESP_LOGI(TAG, "Current time: %s", time.c_str()); + Context::getContext()->vfd->setDisplayString(time); + ESP_LOGI(TAG, "Current time: \"%s\"", time.c_str()); + } + else if (bits & EventGroup::DISPLAY_UPDATE) + { + Context::getContext()->vfd->update(); } else { diff --git a/firmware/main/clock_core_event.hpp b/firmware/main/clock_core_event.hpp index ec36192..5f1e986 100644 --- a/firmware/main/clock_core_event.hpp +++ b/firmware/main/clock_core_event.hpp @@ -15,7 +15,8 @@ struct EventGroup static constexpr EventBits_t WIFI_CONNECTED = BIT1; static constexpr EventBits_t SNTP_SYNCED = BIT2; static constexpr EventBits_t CLOCK_UPDATE = BIT3; - static constexpr EventBits_t ALL_EVENTS = WIFI_FAILED | WIFI_CONNECTED | SNTP_SYNCED | CLOCK_UPDATE; + static constexpr EventBits_t DISPLAY_UPDATE = BIT4; + static constexpr EventBits_t ALL_EVENTS = WIFI_FAILED | WIFI_CONNECTED | SNTP_SYNCED | CLOCK_UPDATE | DISPLAY_UPDATE; EventGroupHandle_t rtos_event_group = nullptr; diff --git a/firmware/main/vfd_driver.cpp b/firmware/main/vfd_driver.cpp new file mode 100644 index 0000000..6932551 --- /dev/null +++ b/firmware/main/vfd_driver.cpp @@ -0,0 +1,179 @@ +// +// Created by erki on 17/02/24. +// + +#include "vfd_driver.hpp" + +#include + +#include +#include +#include + +namespace +{ + +template +constexpr T bitValue(const T& shift) +{ + return T(T(1) << shift); +} + +constexpr auto SEG_A = bitValue(0); +constexpr auto SEG_B = bitValue(1); +constexpr auto SEG_C = bitValue(2); +constexpr auto SEG_D = bitValue(3); +constexpr auto SEG_E = bitValue(4); +constexpr auto SEG_F = bitValue(5); +constexpr auto SEG_G = bitValue(6); +constexpr auto SEG_DEGREE = bitValue(7); +constexpr auto SEG_MINUS = SEG_G; +constexpr auto SEG_DOT = bitValue(7); + +constexpr auto GRID_0 = bitValue(8); +constexpr auto GRID_1 = bitValue(9); +constexpr auto GRID_2 = bitValue(10); +constexpr auto GRID_3 = bitValue(11); +constexpr auto GRID_4 = bitValue(12); +constexpr auto GRID_5 = bitValue(13); +constexpr auto GRID_6 = bitValue(14); +constexpr auto GRID_7 = bitValue(15); +constexpr auto GRID_8 = bitValue(16); + +constexpr auto DIGIT_0 = SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F; +constexpr auto DIGIT_1 = SEG_B | SEG_C; +constexpr auto DIGIT_2 = SEG_A | SEG_B | SEG_D | SEG_E | SEG_G; +constexpr auto DIGIT_3 = SEG_A | SEG_B | SEG_C | SEG_D | SEG_G; +constexpr auto DIGIT_4 = SEG_B | SEG_C | SEG_F | SEG_G; +constexpr auto DIGIT_5 = SEG_A | SEG_C | SEG_D | SEG_F | SEG_G; +constexpr auto DIGIT_6 = SEG_A | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G; +constexpr auto DIGIT_7 = SEG_A | SEG_B | SEG_C; +constexpr auto DIGIT_8 = SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G; +constexpr auto DIGIT_9 = SEG_A | SEG_B | SEG_C | SEG_D | SEG_F | SEG_G; +constexpr auto DIGIT_A = SEG_A | SEG_B | SEG_C | SEG_E | SEG_F | SEG_G; +constexpr auto DIGIT_B = SEG_C | SEG_D | SEG_E | SEG_F | SEG_G; +constexpr auto DIGIT_C = SEG_A | SEG_D | SEG_E | SEG_F; +constexpr auto DIGIT_D = SEG_B | SEG_C | SEG_D | SEG_E | SEG_G; +constexpr auto DIGIT_E = SEG_A | SEG_D | SEG_E | SEG_F | SEG_G; +constexpr auto DIGIT_F = SEG_A | SEG_E | SEG_F | SEG_G; +constexpr auto DIGIT_ALL = SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G | SEG_DOT; + +} + +VfdDriver::VfdDriver(spi_host_device_t spi_host, Hal::Gpio load, Hal::Gpio blank, transaction_cb_t spi_post_up_cb) + : load_(load) + , blank_(blank) +{ + auto dev_config = Utility::zeroInitialized(); + dev_config.clock_source = SPI_CLK_SRC_DEFAULT; + dev_config.clock_speed_hz = 800000; // 800kHz + dev_config.spics_io_num = -1; + dev_config.queue_size = 3; + dev_config.pre_cb = nullptr; + dev_config.post_cb = spi_post_up_cb; + + ESP_ERROR_CHECK(spi_bus_add_device(spi_host, &dev_config, &spi_device_)); + + display_data_.fill(0); +} + +esp_err_t VfdDriver::defaultInitializeBus(spi_host_device_t spi_host, gpio_num_t clock, gpio_num_t mosi, std::optional miso) +{ + spi_bus_config_t bus_config; + bus_config.sclk_io_num = clock; + bus_config.mosi_io_num = mosi; + bus_config.miso_io_num = miso ? *miso : -1; + bus_config.quadwp_io_num = -1; + bus_config.quadhd_io_num = -1; + bus_config.max_transfer_sz = 0; + bus_config.flags = 0; + bus_config.intr_flags = 0; + + return spi_bus_initialize(spi_host, &bus_config, SPI_DMA_CH_AUTO); +} + +void VfdDriver::update() +{ + load_.set(false); + + const std::uint32_t raw = getRawCharAndAdvance_(); + spiWrite_(raw); + + load_.set(true); +} + +void VfdDriver::updateNonBlocking() +{ + load_.set(false); + + const std::uint32_t raw = getRawCharAndAdvance_(); + spiWriteNonBlocking_(raw); +} + +void VfdDriver::spiWrite_(std::uint32_t raw) +{ + std::memcpy(buffer_data_.data(), &raw, buffer_data_.size()); + + auto transaction = Utility::zeroInitialized(); + transaction.length = DISPLAY_BUFFER_LEN * 8; + transaction.tx_buffer = buffer_data_.data(); + transaction.rx_buffer = buffer_data_.data(); + + spi_device_polling_transmit(spi_device_, &transaction); +} + +void VfdDriver::spiWriteNonBlocking_(std::uint32_t raw) +{ + std::memcpy(buffer_data_.data(), &raw, buffer_data_.size()); + + auto transaction = Utility::zeroInitialized(); + transaction.length = DISPLAY_BUFFER_LEN * 8; + transaction.tx_buffer = buffer_data_.data(); + transaction.rx_buffer = buffer_data_.data(); + + spi_device_queue_trans(spi_device_, &transaction, 0); +} + +std::uint32_t VfdDriver::getRawCharAndAdvance_() +{ + constexpr std::uint32_t grids[] = { + GRID_8, GRID_7, GRID_6, GRID_5, GRID_4, GRID_3, GRID_2, GRID_1, GRID_0 + }; + + auto char_to_code = [](char c) -> std::uint32_t + { + c = std::isalpha(c) ? std::tolower(c) : c; + + constexpr std::uint32_t characters[] = { + DIGIT_A, DIGIT_B, DIGIT_C, DIGIT_D, DIGIT_E, DIGIT_F + }; + constexpr std::uint32_t numbers[] = { + DIGIT_0, DIGIT_1, DIGIT_2, DIGIT_3, DIGIT_4, DIGIT_5, DIGIT_6, DIGIT_7, DIGIT_8, DIGIT_9 + }; + + if (c <= 'f' && c >= 'a') + return characters[c - 'a']; + else if (c <= '9' && c >= '0') + return numbers[c - '0']; + else if (c == '.') + return SEG_DOT; + else if (c == '-') + return SEG_MINUS; + else if (c == '*') + return SEG_DEGREE; + else if (c == ' ') + return 0; + else + return 0; + }; + + const char& to_write = display_data_[display_index_]; + + const std::uint32_t raw = grids[display_index_] | char_to_code(to_write); + + display_index_++; + if (display_index_ >= DISPLAY_BUFFER_LEN) + display_index_ = 0; + + return raw; +} diff --git a/firmware/main/vfd_driver.hpp b/firmware/main/vfd_driver.hpp new file mode 100644 index 0000000..1b204ac --- /dev/null +++ b/firmware/main/vfd_driver.hpp @@ -0,0 +1,61 @@ +// +// Created by erki on 17/02/24. +// + +#pragma once + +#include + +#include +#include + +#include "etl/string.h" +#include + +namespace Hal = Peripherals::Hal::Esp; + +class VfdDriver +{ +public: + static constexpr std::size_t DISPLAY_LEN = 9; + static constexpr std::size_t DISPLAY_BUFFER_LEN = 3; + + VfdDriver() = delete; + VfdDriver(spi_host_device_t spi_host, Hal::Gpio load, Hal::Gpio blank, transaction_cb_t spi_post_up_cb = nullptr); + + static esp_err_t defaultInitializeBus(spi_host_device_t spi_host, gpio_num_t clock, gpio_num_t mosi, std::optional miso); + + void update(); + void updateNonBlocking(); + + void setDisplayString(const etl::istring& string) + { + display_data_.fill(0); + const auto max = std::min(string.size(), DISPLAY_LEN); + for (auto i = max; i < max; i++) + display_data_[i] = string[i]; + } + + void enableBlank() + { + blank_.set(true); + } + + void disableBlank() + { + blank_.set(false); + } + +private: + spi_device_handle_t spi_device_ = nullptr; + Hal::Gpio load_; + Hal::Gpio blank_; + + etl::array display_data_; + std::array buffer_data_; + std::size_t display_index_ = 0; + + void spiWrite_(std::uint32_t raw); + void spiWriteNonBlocking_(std::uint32_t raw); + std::uint32_t getRawCharAndAdvance_(); +};