Firmware: VFD driver initial implementation
This commit is contained in:
parent
b1ae5dcd48
commit
50cba9c2c7
@ -1 +1 @@
|
||||
Subproject commit 470ad7537695229e57f2332fdd54a96fed3e556a
|
||||
Subproject commit af9db5a1b013938dc378c092ed1e12d0266348b9
|
||||
@ -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"
|
||||
|
||||
@ -7,18 +7,20 @@
|
||||
#include "esp_expected.hpp"
|
||||
#include "clock_core_event.hpp"
|
||||
#include "string_helpers.hpp"
|
||||
#include "vfd_driver.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <etl/string.h>
|
||||
|
||||
#include <driver/gptimer.h>
|
||||
#include <esp_clk_tree.h>
|
||||
#include <esp_event.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_netif.h>
|
||||
#include <esp_netif_sntp.h>
|
||||
#include <esp_wifi.h>
|
||||
|
||||
#include <peripherals_hal_esp.hpp>
|
||||
#include <utility_bytes.hpp>
|
||||
|
||||
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<void, esp_err_t> 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<Context*>(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<gptimer_config_t>();
|
||||
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<gptimer_alarm_config_t>();
|
||||
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
|
||||
{
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
179
firmware/main/vfd_driver.cpp
Normal file
179
firmware/main/vfd_driver.cpp
Normal file
@ -0,0 +1,179 @@
|
||||
//
|
||||
// Created by erki on 17/02/24.
|
||||
//
|
||||
|
||||
#include "vfd_driver.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <esp_clk_tree.h>
|
||||
#include <esp_log.h>
|
||||
#include <utility_bytes.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
constexpr T bitValue(const T& shift)
|
||||
{
|
||||
return T(T(1) << shift);
|
||||
}
|
||||
|
||||
constexpr auto SEG_A = bitValue<std::uint32_t>(0);
|
||||
constexpr auto SEG_B = bitValue<std::uint32_t>(1);
|
||||
constexpr auto SEG_C = bitValue<std::uint32_t>(2);
|
||||
constexpr auto SEG_D = bitValue<std::uint32_t>(3);
|
||||
constexpr auto SEG_E = bitValue<std::uint32_t>(4);
|
||||
constexpr auto SEG_F = bitValue<std::uint32_t>(5);
|
||||
constexpr auto SEG_G = bitValue<std::uint32_t>(6);
|
||||
constexpr auto SEG_DEGREE = bitValue<std::uint32_t>(7);
|
||||
constexpr auto SEG_MINUS = SEG_G;
|
||||
constexpr auto SEG_DOT = bitValue<std::uint32_t>(7);
|
||||
|
||||
constexpr auto GRID_0 = bitValue<std::uint32_t>(8);
|
||||
constexpr auto GRID_1 = bitValue<std::uint32_t>(9);
|
||||
constexpr auto GRID_2 = bitValue<std::uint32_t>(10);
|
||||
constexpr auto GRID_3 = bitValue<std::uint32_t>(11);
|
||||
constexpr auto GRID_4 = bitValue<std::uint32_t>(12);
|
||||
constexpr auto GRID_5 = bitValue<std::uint32_t>(13);
|
||||
constexpr auto GRID_6 = bitValue<std::uint32_t>(14);
|
||||
constexpr auto GRID_7 = bitValue<std::uint32_t>(15);
|
||||
constexpr auto GRID_8 = bitValue<std::uint32_t>(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<spi_device_interface_config_t>();
|
||||
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<gpio_num_t> 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<spi_transaction_t>();
|
||||
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<spi_transaction_t>();
|
||||
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;
|
||||
}
|
||||
61
firmware/main/vfd_driver.hpp
Normal file
61
firmware/main/vfd_driver.hpp
Normal file
@ -0,0 +1,61 @@
|
||||
//
|
||||
// Created by erki on 17/02/24.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <peripherals_hal_esp.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
|
||||
#include "etl/string.h"
|
||||
#include <driver/spi_master.h>
|
||||
|
||||
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<gpio_num_t> 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<char, DISPLAY_LEN> display_data_;
|
||||
std::array<std::uint8_t, DISPLAY_BUFFER_LEN> buffer_data_;
|
||||
std::size_t display_index_ = 0;
|
||||
|
||||
void spiWrite_(std::uint32_t raw);
|
||||
void spiWriteNonBlocking_(std::uint32_t raw);
|
||||
std::uint32_t getRawCharAndAdvance_();
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user