game timers
This commit is contained in:
parent
b3bcb4108d
commit
75052174a6
@ -4,10 +4,12 @@
|
||||
#include "blk_box_drivers/inputs.hpp"
|
||||
#include "blk_box_drivers/leds.hpp"
|
||||
#include "blk_box_drivers/char_lcd.hpp"
|
||||
#include "blk_box_drivers/ssegs.hpp"
|
||||
|
||||
void init_blk_box(BlkBoxInitConfig cfg) {
|
||||
init_main_i2c();
|
||||
init_expander();
|
||||
init_leds();
|
||||
init_lcd();
|
||||
init_ssegs();
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ set(SOURCES
|
||||
"i2c.cpp"
|
||||
"lcd2004.cpp"
|
||||
"leds.cpp"
|
||||
"ssegs.cpp"
|
||||
"tm1640.cpp"
|
||||
)
|
||||
|
||||
target_sources(${COMPONENT_LIB} PRIVATE ${SOURCES})
|
||||
|
||||
484
drivers/ssegs.cpp
Normal file
484
drivers/ssegs.cpp
Normal file
@ -0,0 +1,484 @@
|
||||
#include "blk_box_drivers/ssegs.hpp"
|
||||
|
||||
#include "tm1640.hpp"
|
||||
#include "pins.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <tuple>
|
||||
|
||||
const static uint32_t TICKER_PERIOD_MS = 100;
|
||||
|
||||
const static uint8_t MODULE_IDX = 0;
|
||||
const static uint8_t GAME_IDX = 4;
|
||||
|
||||
TM1640 ssegs(PIN_SSEG_CLK, PIN_SSEG_DAT);
|
||||
const static size_t CMD_QUEUE_SIZE = 10;
|
||||
QueueHandle_t cmd_queue;
|
||||
|
||||
std::atomic<int32_t> game_time = 0;
|
||||
std::atomic<int32_t> module_time = 0;
|
||||
|
||||
// for notifying users of events
|
||||
const static uint32_t EVENT_CMDS_FLUSHED = (1 << 0);
|
||||
const static uint32_t EVENT_MODULE_POSITIVE = (1 << 1);
|
||||
const static uint32_t EVENT_MODULE_ZERO_NEG = (1 << 2);
|
||||
const static uint32_t EVENT_GAME_POSITIVE = (1 << 3);
|
||||
const static uint32_t EVENT_GAME_ZERO_NEG = (1 << 4);
|
||||
|
||||
EventGroupHandle_t ssegs_event_group;
|
||||
|
||||
const static char* TAG = "ssegs";
|
||||
|
||||
/// Uses a compare-exchange loop to do a saturating subtraction on an atomic int32_t. Returns the old and new values.
|
||||
std::pair<int32_t, int32_t> saturating_sub(std::atomic<int32_t>& v, int32_t sub) {
|
||||
int32_t cur = v.load(std::memory_order_relaxed);
|
||||
while (true) {
|
||||
int32_t desired = (cur <= sub) ? 0 : cur - sub;
|
||||
if (v.compare_exchange_weak(cur, desired,
|
||||
std::memory_order_relaxed)) {
|
||||
return {cur, desired}; // {old value, new value}
|
||||
}
|
||||
// cur is updated automatically with latest value on failure
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the segment buffer to reflect the current time.
|
||||
///
|
||||
/// Returns `true` if the buffer has changed and needs to be redrawn.
|
||||
///
|
||||
/// `seg_buf.len()` should be >= 4.
|
||||
bool update_segments(int32_t last_time, int32_t current_time, uint8_t seg_buf[4]) {
|
||||
const uint32_t MILLIS_10S = 100;
|
||||
const uint32_t SECOND = 1000;
|
||||
const uint32_t SECOND_10S = SECOND * 10;
|
||||
const uint32_t MINUTE = 60 * SECOND;
|
||||
const uint32_t MINUTE_10S = 10 * MINUTE;
|
||||
const uint32_t HOUR = 60 * MINUTE;
|
||||
const uint32_t HOUR_10S = 10 * HOUR;
|
||||
|
||||
uint32_t time = std::abs(current_time);
|
||||
|
||||
if (time > HOUR) {
|
||||
// HH.MM
|
||||
if ((current_time / MINUTE) == (last_time / MINUTE)) {
|
||||
// no change neccesary
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t h1 = (time / HOUR_10S) % 10;
|
||||
uint8_t h0 = (time / HOUR) % 10;
|
||||
uint8_t minutes = (time / MINUTE) % 60;
|
||||
uint8_t m1 = minutes / 10;
|
||||
uint8_t m0 = minutes % 10;
|
||||
|
||||
seg_buf[0] = SSegController::FONT_HEX[h1];
|
||||
seg_buf[1] = SSegController::FONT_HEX[h0] | SSegController::BIT_MASK_DP;
|
||||
seg_buf[2] = SSegController::FONT_HEX[m1];
|
||||
seg_buf[3] = SSegController::FONT_HEX[m0];
|
||||
return true;
|
||||
} else if (time > MINUTE) {
|
||||
// MM.SS
|
||||
if ((current_time / SECOND) == (last_time / SECOND)) {
|
||||
// no change neccesary
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t m1 = (time / MINUTE_10S) % 10;
|
||||
uint8_t m0 = (time / MINUTE) % 10;
|
||||
uint8_t seconds = (time / SECOND) % 60;
|
||||
uint8_t s1 = seconds / 10;
|
||||
uint8_t s0 = seconds % 10;
|
||||
|
||||
seg_buf[0] = SSegController::FONT_HEX[m1];
|
||||
seg_buf[1] = SSegController::FONT_HEX[m0] | SSegController::BIT_MASK_DP;
|
||||
seg_buf[2] = SSegController::FONT_HEX[s1];
|
||||
seg_buf[3] = SSegController::FONT_HEX[s0];
|
||||
return true;
|
||||
} else {
|
||||
// SS.m
|
||||
if ((current_time / MILLIS_10S) == (last_time / MILLIS_10S)) {
|
||||
// no change neccesary
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t s1 = (time / SECOND_10S) % 10;
|
||||
uint8_t s0 = (time / SECOND) % 10;
|
||||
uint8_t m1 = (time / MILLIS_10S) % 10;
|
||||
|
||||
seg_buf[0] = 0; // unused digit
|
||||
seg_buf[1] = SSegController::FONT_HEX[s1];
|
||||
seg_buf[2] = SSegController::FONT_HEX[s0] | SSegController::BIT_MASK_DP;
|
||||
seg_buf[3] = SSegController::FONT_HEX[m1];
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void timer_task(void* arg) {
|
||||
(void) arg;
|
||||
|
||||
const TickType_t ticker_period_ticks = pdMS_TO_TICKS(TICKER_PERIOD_MS);
|
||||
|
||||
ESP_LOGI(TAG, "sseg timer task starting...");
|
||||
|
||||
bool game_en = false;
|
||||
bool game_running = false;
|
||||
bool game_rollover = true;
|
||||
bool module_en = false;
|
||||
bool module_running = false;
|
||||
bool module_rollover = false;
|
||||
|
||||
uint8_t seg_buf[4] = {0};
|
||||
|
||||
TickType_t last_wake_time = xTaskGetTickCount();
|
||||
SSegCommand cmd;
|
||||
|
||||
while (true) {
|
||||
TickType_t elapsed = xTaskGetTickCount() - last_wake_time;
|
||||
if ((ticker_period_ticks > elapsed) && (xQueueReceive(cmd_queue, &cmd, ticker_period_ticks - elapsed) == pdPASS)) {
|
||||
// command received
|
||||
ESP_LOGI(TAG, "sseg command received");
|
||||
|
||||
switch (cmd.type) {
|
||||
case SSegCommand::Type::SetIntensity: {
|
||||
uint8_t intensity = std::get<uint8_t>(cmd.data);
|
||||
ssegs.set_intensity(intensity);
|
||||
break;
|
||||
}
|
||||
case SSegCommand::Type::EnableGameTimer: {
|
||||
game_en = true;
|
||||
int32_t game_time_val = game_time.load(std::memory_order_acquire);
|
||||
if (update_segments(std::numeric_limits<int32_t>::max(), game_time_val, seg_buf)) {
|
||||
ssegs.set_digits(GAME_IDX, seg_buf, 4);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SSegCommand::Type::DisableGameTimer: {
|
||||
game_en = false;
|
||||
game_running = false;
|
||||
game_time.store(0, std::memory_order_release);
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_GAME_POSITIVE | EVENT_GAME_ZERO_NEG);
|
||||
for (uint8_t& seg : seg_buf) {
|
||||
seg = 0;
|
||||
}
|
||||
ssegs.set_digits(GAME_IDX, seg_buf, 4);
|
||||
break;
|
||||
}
|
||||
case SSegCommand::Type::StartGameTimer:
|
||||
game_running = true;
|
||||
break;
|
||||
case SSegCommand::Type::StopGameTimer:
|
||||
game_running = false;
|
||||
break;
|
||||
case SSegCommand::Type::SetGameTime: {
|
||||
int32_t new_time = std::get<int32_t>(cmd.data);
|
||||
int32_t last_time = game_time.exchange(new_time, std::memory_order_acq_rel);
|
||||
if (new_time > 0) {
|
||||
xEventGroupSetBits(ssegs_event_group, EVENT_GAME_POSITIVE);
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_GAME_ZERO_NEG);
|
||||
} else {
|
||||
xEventGroupSetBits(ssegs_event_group, EVENT_GAME_ZERO_NEG);
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_GAME_POSITIVE);
|
||||
}
|
||||
if (game_en) {
|
||||
if (update_segments(last_time, new_time, seg_buf)) {
|
||||
ssegs.set_digits(GAME_IDX, seg_buf, 4);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SSegCommand::Type::EnableModuleTimer: {
|
||||
module_en = true;
|
||||
int32_t module_time_val = module_time.load(std::memory_order_acquire);
|
||||
if (update_segments(std::numeric_limits<int32_t>::max(), module_time_val, seg_buf)) {
|
||||
ssegs.set_digits(MODULE_IDX, seg_buf, 4);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SSegCommand::Type::DisableModuleTimer: {
|
||||
module_en = false;
|
||||
module_running = false;
|
||||
module_time.store(0, std::memory_order_release);
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_MODULE_POSITIVE | EVENT_MODULE_ZERO_NEG);
|
||||
for (uint8_t& seg : seg_buf) {
|
||||
seg = 0;
|
||||
}
|
||||
ssegs.set_digits(MODULE_IDX, seg_buf, 4);
|
||||
break;
|
||||
}
|
||||
case SSegCommand::Type::StartModuleTimer:
|
||||
module_running = true;
|
||||
break;
|
||||
case SSegCommand::Type::StopModuleTimer:
|
||||
module_running = false;
|
||||
break;
|
||||
case SSegCommand::Type::SetModuleTime: {
|
||||
int32_t new_time = std::get<int32_t>(cmd.data);
|
||||
int32_t last_time = module_time.exchange(new_time, std::memory_order_acq_rel);
|
||||
if (new_time > 0) {
|
||||
xEventGroupSetBits(ssegs_event_group, EVENT_MODULE_POSITIVE);
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_MODULE_ZERO_NEG);
|
||||
} else {
|
||||
xEventGroupSetBits(ssegs_event_group, EVENT_MODULE_ZERO_NEG);
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_MODULE_POSITIVE);
|
||||
}
|
||||
if (module_en) {
|
||||
if (update_segments(last_time, new_time, seg_buf)) {
|
||||
ssegs.set_digits(MODULE_IDX, seg_buf, 4);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SSegCommand::Type::SetGameRaw: {
|
||||
std::array<uint8_t, 4> raw = std::get<std::array<uint8_t, 4>>(cmd.data);
|
||||
ssegs.set_digits(GAME_IDX, raw.data(), 4);
|
||||
break;
|
||||
}
|
||||
case SSegCommand::Type::SetGameDigit: {
|
||||
auto [digit, value] = std::get<std::pair<uint8_t, uint8_t>>(cmd.data);
|
||||
ssegs.set_digit(GAME_IDX + digit, value);
|
||||
break;
|
||||
}
|
||||
case SSegCommand::Type::SetModuleRaw: {
|
||||
std::array<uint8_t, 4> raw = std::get<std::array<uint8_t, 4>>(cmd.data);
|
||||
ssegs.set_digits(MODULE_IDX, raw.data(), 4);
|
||||
break;
|
||||
}
|
||||
case SSegCommand::Type::SetModuleDigit: {
|
||||
auto [digit, value] = std::get<std::pair<uint8_t, uint8_t>>(cmd.data);
|
||||
ssegs.set_digit(MODULE_IDX + digit, value);
|
||||
break;
|
||||
}
|
||||
|
||||
case SSegCommand::Type::SetGameRollover: {
|
||||
bool rollover = std::get<bool>(cmd.data);
|
||||
game_rollover = rollover;
|
||||
break;
|
||||
}
|
||||
case SSegCommand::Type::SetModuleRollover: {
|
||||
bool rollover = std::get<bool>(cmd.data);
|
||||
module_rollover = rollover;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (uxQueueMessagesWaiting(cmd_queue) == 0) {
|
||||
xEventGroupSetBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
}
|
||||
} else {
|
||||
// ticker finished
|
||||
last_wake_time += pdMS_TO_TICKS(TICKER_PERIOD_MS);
|
||||
|
||||
bool update_module = module_en && module_running;
|
||||
bool update_game = game_en && game_running;
|
||||
|
||||
// ESP_LOGI(TAG, "ticker ticked: update_game=%d, update_module=%d", update_game, update_module);
|
||||
|
||||
if (update_module) {
|
||||
int32_t old_time;
|
||||
int32_t new_time;
|
||||
if (module_rollover) {
|
||||
old_time = module_time.fetch_sub(TICKER_PERIOD_MS);
|
||||
new_time = old_time - TICKER_PERIOD_MS; // fetch_sub returns old value
|
||||
} else {
|
||||
std::tie(old_time, new_time) = saturating_sub(module_time, TICKER_PERIOD_MS);
|
||||
}
|
||||
|
||||
if (new_time > 0) {
|
||||
xEventGroupSetBits(ssegs_event_group, EVENT_MODULE_POSITIVE);
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_MODULE_ZERO_NEG);
|
||||
} else {
|
||||
xEventGroupSetBits(ssegs_event_group, EVENT_MODULE_ZERO_NEG);
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_MODULE_POSITIVE);
|
||||
}
|
||||
|
||||
if (update_segments(old_time, new_time, seg_buf)) {
|
||||
ssegs.set_digits(MODULE_IDX, seg_buf, 4);
|
||||
}
|
||||
if (new_time == 0 && !module_rollover) {
|
||||
// we've hit 0 and are not rolling over
|
||||
module_running = false;
|
||||
}
|
||||
}
|
||||
if (update_game) {
|
||||
int32_t old_time;
|
||||
int32_t new_time;
|
||||
if (game_rollover) {
|
||||
old_time = game_time.fetch_sub(TICKER_PERIOD_MS);
|
||||
new_time = old_time - TICKER_PERIOD_MS; // fetch_sub returns old value
|
||||
} else {
|
||||
std::tie(old_time, new_time) = saturating_sub(game_time, TICKER_PERIOD_MS);
|
||||
}
|
||||
|
||||
if (new_time > 0) {
|
||||
xEventGroupSetBits(ssegs_event_group, EVENT_GAME_POSITIVE);
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_GAME_ZERO_NEG);
|
||||
} else {
|
||||
xEventGroupSetBits(ssegs_event_group, EVENT_GAME_ZERO_NEG);
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_GAME_POSITIVE);
|
||||
}
|
||||
|
||||
if (update_segments(old_time, new_time, seg_buf)) {
|
||||
ssegs.set_digits(GAME_IDX, seg_buf, 4);
|
||||
}
|
||||
if (new_time == 0 && !game_rollover) {
|
||||
// we've hit 0 and are not rolling over
|
||||
game_running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SSegController static method implementations
|
||||
void SSegController::enable_game_timer() {
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
SSegCommand cmd = SSegCommand::EnableGameTimer();
|
||||
xQueueSend(cmd_queue, &cmd, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::disable_game_timer() {
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
SSegCommand cmd = SSegCommand::DisableGameTimer();
|
||||
xQueueSend(cmd_queue, &cmd, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::start_game_timer() {
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
SSegCommand cmd = SSegCommand::StartGameTimer();
|
||||
xQueueSend(cmd_queue, &cmd, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::stop_game_timer() {
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
SSegCommand cmd = SSegCommand::StopGameTimer();
|
||||
xQueueSend(cmd_queue, &cmd, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::set_game_time(int32_t millis) {
|
||||
// Align to TICKER_PERIOD_MS
|
||||
millis = millis - (millis % TICKER_PERIOD_MS);
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
SSegCommand cmd = SSegCommand::SetGameTime(millis);
|
||||
xQueueSend(cmd_queue, &cmd, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::enable_module_timer() {
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
SSegCommand cmd = SSegCommand::EnableModuleTimer();
|
||||
xQueueSend(cmd_queue, &cmd, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::disable_module_timer() {
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
SSegCommand cmd = SSegCommand::DisableModuleTimer();
|
||||
xQueueSend(cmd_queue, &cmd, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::start_module_timer() {
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
SSegCommand cmd = SSegCommand::StartModuleTimer();
|
||||
xQueueSend(cmd_queue, &cmd, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::stop_module_timer() {
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
SSegCommand cmd = SSegCommand::StopModuleTimer();
|
||||
xQueueSend(cmd_queue, &cmd, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::set_module_time(int32_t millis) {
|
||||
// Align to TICKER_PERIOD_MS
|
||||
millis = millis - (millis % TICKER_PERIOD_MS);
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
SSegCommand cmd = SSegCommand::SetModuleTime(millis);
|
||||
xQueueSend(cmd_queue, &cmd, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::set_game_raw(const std::array<uint8_t, 4>& segments) {
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
SSegCommand cmd = SSegCommand::SetGameRaw(segments);
|
||||
xQueueSend(cmd_queue, &cmd, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::set_game_digit_raw(uint8_t digit, uint8_t segments) {
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
SSegCommand cmd = SSegCommand::SetGameDigit(digit, segments);
|
||||
xQueueSend(cmd_queue, &cmd, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::set_module_raw(const std::array<uint8_t, 4>& segments) {
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
SSegCommand cmd = SSegCommand::SetModuleRaw(segments);
|
||||
xQueueSend(cmd_queue, &cmd, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::set_module_digit_raw(uint8_t digit, uint8_t segments) {
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
SSegCommand cmd = SSegCommand::SetModuleDigit(digit, segments);
|
||||
xQueueSend(cmd_queue, &cmd, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::game_timer_rollover(bool rollover) {
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
SSegCommand cmd = SSegCommand::SetGameRollover(rollover);
|
||||
xQueueSend(cmd_queue, &cmd, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::module_timer_rollover(bool rollover) {
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
SSegCommand cmd = SSegCommand::SetModuleRollover(rollover);
|
||||
xQueueSend(cmd_queue, &cmd, portMAX_DELAY);
|
||||
}
|
||||
|
||||
int32_t SSegController::get_game_time() {
|
||||
return game_time.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
int32_t SSegController::get_module_time() {
|
||||
return module_time.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void SSegController::flush() {
|
||||
xEventGroupWaitBits(ssegs_event_group, EVENT_CMDS_FLUSHED, pdFALSE, pdTRUE, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::wait_game_timer_done() {
|
||||
xEventGroupWaitBits(ssegs_event_group, EVENT_GAME_ZERO_NEG, pdTRUE, pdFALSE, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::wait_module_timer_done() {
|
||||
xEventGroupWaitBits(ssegs_event_group, EVENT_MODULE_ZERO_NEG, pdTRUE, pdFALSE, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void SSegController::set_intensity(uint8_t intensity) {
|
||||
xEventGroupClearBits(ssegs_event_group, EVENT_CMDS_FLUSHED);
|
||||
SSegCommand cmd = SSegCommand::SetIntensity(intensity);
|
||||
xQueueSend(cmd_queue, &cmd, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void init_ssegs() {
|
||||
ssegs.init();
|
||||
|
||||
cmd_queue = xQueueCreate(CMD_QUEUE_SIZE, sizeof(SSegCommand));
|
||||
if (cmd_queue == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to create command queue!");
|
||||
return;
|
||||
}
|
||||
|
||||
ssegs_event_group = xEventGroupCreate();
|
||||
if (ssegs_event_group == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to create event group!");
|
||||
return;
|
||||
}
|
||||
|
||||
xTaskCreate(timer_task, "ssegs_timer_task", 4096, NULL, 4, NULL);
|
||||
}
|
||||
|
||||
|
||||
117
drivers/tm1640.cpp
Normal file
117
drivers/tm1640.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
#include "tm1640.hpp"
|
||||
#include <vector>
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_rom_sys.h"
|
||||
|
||||
// Constants
|
||||
static const uint8_t CMD_DATA_AUTO = 0x40;
|
||||
static const uint8_t CMD_DATA_FIXED = 0x44;
|
||||
static const uint8_t CMD_DISPLAY = 0x80;
|
||||
static const uint8_t CMD_ADDRESS = 0xC0;
|
||||
|
||||
// TODO: we could use the RMT interface to do this more efficiently.
|
||||
|
||||
TM1640::TM1640(gpio_num_t clk_pin, gpio_num_t dio_pin) : clk_pin(clk_pin), dio_pin(dio_pin), intensity(0x0F) {
|
||||
// Configure pins as output
|
||||
gpio_config_t io_conf = {};
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << clk_pin) | (1ULL << dio_pin);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
gpio_config(&io_conf);
|
||||
|
||||
// Set pins high
|
||||
gpio_set_level(clk_pin, 1);
|
||||
gpio_set_level(dio_pin, 1);
|
||||
}
|
||||
|
||||
void TM1640::bit_delay() {
|
||||
esp_rom_delay_us(1);
|
||||
}
|
||||
|
||||
void TM1640::start() {
|
||||
gpio_set_level(dio_pin, 0);
|
||||
gpio_set_level(clk_pin, 0);
|
||||
bit_delay();
|
||||
}
|
||||
|
||||
void TM1640::stop() {
|
||||
gpio_set_level(dio_pin, 0);
|
||||
bit_delay();
|
||||
gpio_set_level(clk_pin, 1);
|
||||
gpio_set_level(dio_pin, 1);
|
||||
bit_delay();
|
||||
}
|
||||
|
||||
void TM1640::shift_out(uint8_t data) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
gpio_set_level(dio_pin, data & 1);
|
||||
data >>= 1;
|
||||
bit_delay();
|
||||
gpio_set_level(clk_pin, 1);
|
||||
bit_delay();
|
||||
gpio_set_level(clk_pin, 0);
|
||||
bit_delay();
|
||||
}
|
||||
}
|
||||
|
||||
void TM1640::send(uint8_t* data, size_t len) {
|
||||
start();
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
shift_out(data[i]);
|
||||
}
|
||||
stop();
|
||||
}
|
||||
|
||||
void TM1640::init() {
|
||||
clear_display();
|
||||
}
|
||||
|
||||
void TM1640::clear_display() {
|
||||
uint8_t data1[] = {CMD_DATA_AUTO};
|
||||
send(data1, 1);
|
||||
uint8_t data2[] = {CMD_ADDRESS, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
send(data2, 9);
|
||||
uint8_t data3 = CMD_DISPLAY | intensity;
|
||||
send(&data3, 1);
|
||||
}
|
||||
|
||||
// TODO: can these become all one send??
|
||||
// other functions too
|
||||
void TM1640::set_digit(uint8_t digit, uint8_t segments) {
|
||||
uint8_t data1[] = {CMD_DATA_FIXED};
|
||||
send(data1, 1);
|
||||
uint8_t cmd = CMD_ADDRESS | digit;
|
||||
uint8_t data2[] = {cmd, segments};
|
||||
send(data2, 2);
|
||||
uint8_t data3 = CMD_DISPLAY | intensity;
|
||||
send(&data3, 1);
|
||||
}
|
||||
|
||||
void TM1640::set_digits(uint8_t starting_pos, uint8_t* segments, size_t len) {
|
||||
uint8_t data1[] = {CMD_DATA_AUTO};
|
||||
send(data1, 1);
|
||||
std::vector<uint8_t> data;
|
||||
data.push_back(CMD_ADDRESS | starting_pos);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
data.push_back(segments[i]);
|
||||
}
|
||||
send(data.data(), data.size());
|
||||
uint8_t data3 = CMD_DISPLAY | intensity;
|
||||
send(&data3, 1);
|
||||
}
|
||||
|
||||
void TM1640::set_intensity(uint8_t intensity) {
|
||||
uint8_t new_intensity = intensity & 0x07; // 0-7
|
||||
this->intensity = (this->intensity & 0xF8) | new_intensity;
|
||||
uint8_t cmd = CMD_DISPLAY | this->intensity;
|
||||
send(&cmd, 1);
|
||||
}
|
||||
|
||||
void TM1640::set_display(bool on) {
|
||||
uint8_t display_bit = on ? 0x08 : 0x00;
|
||||
this->intensity = (this->intensity & 0xF7) | display_bit;
|
||||
uint8_t cmd = CMD_DISPLAY | this->intensity;
|
||||
send(&cmd, 1);
|
||||
}
|
||||
47
drivers/tm1640.hpp
Normal file
47
drivers/tm1640.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
#ifndef TM1640_HPP
|
||||
#define TM1640_HPP
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include <stdint.h>
|
||||
|
||||
class TM1640 {
|
||||
gpio_num_t clk_pin;
|
||||
gpio_num_t dio_pin;
|
||||
/// The intensity and display on/off setting.
|
||||
uint8_t intensity;
|
||||
|
||||
void bit_delay();
|
||||
void start();
|
||||
void stop();
|
||||
void shift_out(uint8_t data);
|
||||
void send(uint8_t* data, size_t len);
|
||||
public:
|
||||
TM1640(gpio_num_t clk_pin, gpio_num_t dio_pin);
|
||||
|
||||
/// Initializes the TM1640 7-segment display.
|
||||
void init();
|
||||
|
||||
/// Clears the display by setting all segments to off.
|
||||
void clear_display();
|
||||
|
||||
/// Sets the segments of a single digit.
|
||||
void set_digit(uint8_t digit, uint8_t segments);
|
||||
|
||||
/// Sets the segments of multiple digits starting at `starting_pos`.
|
||||
void set_digits(uint8_t starting_pos, uint8_t* segments, size_t len);
|
||||
|
||||
/// Sets the intensity from 0-7.
|
||||
///
|
||||
/// intensity 0 is still on. To turn the display off, use `set_display`().
|
||||
void set_intensity(uint8_t intensity);
|
||||
|
||||
/// Turns the display on or off.
|
||||
void set_display(bool on);
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
#endif // TM1640_HPP
|
||||
282
include/blk_box_drivers/ssegs.hpp
Normal file
282
include/blk_box_drivers/ssegs.hpp
Normal file
@ -0,0 +1,282 @@
|
||||
#ifndef SSEGS_HPP
|
||||
#define SSEGS_HPP
|
||||
|
||||
#include <variant>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
/// A command to send to the sseg timer controller.
|
||||
struct SSegCommand {
|
||||
enum class Type {
|
||||
SetIntensity,
|
||||
EnableGameTimer,
|
||||
DisableGameTimer,
|
||||
StartGameTimer,
|
||||
StopGameTimer,
|
||||
SetGameTime,
|
||||
EnableModuleTimer,
|
||||
DisableModuleTimer,
|
||||
StartModuleTimer,
|
||||
StopModuleTimer,
|
||||
SetModuleTime,
|
||||
SetGameRaw,
|
||||
SetGameDigit,
|
||||
SetModuleRaw,
|
||||
SetModuleDigit,
|
||||
SetGameRollover,
|
||||
SetModuleRollover,
|
||||
};
|
||||
|
||||
Type type;
|
||||
std::variant<
|
||||
std::monostate, // for commands without data
|
||||
uint8_t, // SetIntensity
|
||||
int32_t, // SetGameTime, SetModuleTime
|
||||
std::array<uint8_t, 4>, // SetGameRaw, SetModuleRaw
|
||||
std::pair<uint8_t, uint8_t>, // SetGameDigit, SetModuleDigit
|
||||
bool // SetGameRollover, SetModuleRollover
|
||||
> data;
|
||||
|
||||
// Constructors for each variant
|
||||
static SSegCommand SetIntensity(uint8_t intensity) {
|
||||
return {Type::SetIntensity, intensity};
|
||||
}
|
||||
|
||||
static SSegCommand EnableGameTimer() {
|
||||
return {Type::EnableGameTimer, std::monostate{}};
|
||||
}
|
||||
|
||||
static SSegCommand DisableGameTimer() {
|
||||
return {Type::DisableGameTimer, std::monostate{}};
|
||||
}
|
||||
|
||||
static SSegCommand StartGameTimer() {
|
||||
return {Type::StartGameTimer, std::monostate{}};
|
||||
}
|
||||
|
||||
static SSegCommand StopGameTimer() {
|
||||
return {Type::StopGameTimer, std::monostate{}};
|
||||
}
|
||||
|
||||
static SSegCommand SetGameTime(int32_t time) {
|
||||
return {Type::SetGameTime, time};
|
||||
}
|
||||
|
||||
static SSegCommand EnableModuleTimer() {
|
||||
return {Type::EnableModuleTimer, std::monostate{}};
|
||||
}
|
||||
|
||||
static SSegCommand DisableModuleTimer() {
|
||||
return {Type::DisableModuleTimer, std::monostate{}};
|
||||
}
|
||||
|
||||
static SSegCommand StartModuleTimer() {
|
||||
return {Type::StartModuleTimer, std::monostate{}};
|
||||
}
|
||||
|
||||
static SSegCommand StopModuleTimer() {
|
||||
return {Type::StopModuleTimer, std::monostate{}};
|
||||
}
|
||||
|
||||
static SSegCommand SetModuleTime(int32_t time) {
|
||||
return {Type::SetModuleTime, time};
|
||||
}
|
||||
|
||||
static SSegCommand SetGameRaw(std::array<uint8_t, 4> raw) {
|
||||
return {Type::SetGameRaw, raw};
|
||||
}
|
||||
|
||||
static SSegCommand SetGameDigit(uint8_t digit, uint8_t value) {
|
||||
return {Type::SetGameDigit, std::make_pair(digit, value)};
|
||||
}
|
||||
|
||||
static SSegCommand SetModuleRaw(std::array<uint8_t, 4> raw) {
|
||||
return {Type::SetModuleRaw, raw};
|
||||
}
|
||||
|
||||
static SSegCommand SetModuleDigit(uint8_t digit, uint8_t value) {
|
||||
return {Type::SetModuleDigit, std::make_pair(digit, value)};
|
||||
}
|
||||
|
||||
static SSegCommand SetGameRollover(bool rollover) {
|
||||
return {Type::SetGameRollover, rollover};
|
||||
}
|
||||
|
||||
static SSegCommand SetModuleRollover(bool rollover) {
|
||||
return {Type::SetModuleRollover, rollover};
|
||||
}
|
||||
};
|
||||
|
||||
class SSegController {
|
||||
public:
|
||||
/// A hexidecimal font for the seven segment displays.
|
||||
constexpr static uint8_t FONT_HEX[16] = {
|
||||
0b00111111, 0b00000110, 0b01011011, 0b01001111, 0b01100110, 0b01101101, 0b01111101,
|
||||
0b00000111, 0b01111111, 0b01101111, 0b01110111, 0b01111100, 0b00111001, 0b01011110,
|
||||
0b01111001, 0b01110001,
|
||||
};
|
||||
/// The mask for the 'A' segment of the display.
|
||||
constexpr static uint8_t BIT_MASK_A = 0b0000'0001;
|
||||
/// The mask for the 'B' segment of the display.
|
||||
constexpr static uint8_t BIT_MASK_B = 0b0000'0010;
|
||||
/// The mask for the 'C' segment of the display.
|
||||
constexpr static uint8_t BIT_MASK_C = 0b0000'0100;
|
||||
/// The mask for the 'D' segment of the display.
|
||||
constexpr static uint8_t BIT_MASK_D = 0b0000'1000;
|
||||
/// The mask for the 'E' segment of the display.
|
||||
constexpr static uint8_t BIT_MASK_E = 0b0001'0000;
|
||||
/// The mask for the 'F' segment of the display.
|
||||
constexpr static uint8_t BIT_MASK_F = 0b0010'0000;
|
||||
/// The mask for the 'G' segment of the display.
|
||||
constexpr static uint8_t BIT_MASK_G = 0b0100'0000;
|
||||
/// The mask for the 'DP' (decimal point) segment of the display.
|
||||
constexpr static uint8_t BIT_MASK_DP = 0b1000'0000;
|
||||
|
||||
/// Enables the game timer.
|
||||
///
|
||||
/// This "gives control" of the game timer over to the
|
||||
/// timer task.
|
||||
///
|
||||
/// This does not start the game timer, only enables it.
|
||||
static void enable_game_timer();
|
||||
|
||||
/// Disables the game timer.
|
||||
///
|
||||
/// This "takes control" of the game timer away from the
|
||||
/// timer task.
|
||||
///
|
||||
/// This also stops the timer, resets the time to 0, and clears
|
||||
/// the display.
|
||||
static void disable_game_timer();
|
||||
|
||||
/// Starts the game timer.
|
||||
///
|
||||
/// This can be called while the game timer is disabled,
|
||||
/// but the timer will not start until it is enabled.
|
||||
///
|
||||
/// Calling this while the timer is disabled can be useful
|
||||
/// if you want it to start counting right away.
|
||||
static void start_game_timer();
|
||||
|
||||
/// Stops the game timer.
|
||||
///
|
||||
/// This can be called while the game timer is disabled,
|
||||
/// but the timer only counts while it is enabled regardless.
|
||||
static void stop_game_timer();
|
||||
|
||||
/// Sets the game time.
|
||||
///
|
||||
/// This can be called even when the game timer is disabled.
|
||||
///
|
||||
/// A negative number will cause the timer to count up.
|
||||
static void set_game_time(int32_t millis);
|
||||
|
||||
/// Enables the module timer.
|
||||
///
|
||||
/// This "gives control" of the module timer over to the
|
||||
/// timer task.
|
||||
///
|
||||
/// This does not start the module timer, only enables it.
|
||||
static void enable_module_timer();
|
||||
|
||||
/// Disables the module timer.
|
||||
///
|
||||
/// This "takes control" of the module timer away from the
|
||||
/// timer task.
|
||||
///
|
||||
/// This also stops the timer, resets the time to 0, and clears
|
||||
/// the display.
|
||||
static void disable_module_timer();
|
||||
|
||||
/// Starts the module timer.
|
||||
///
|
||||
/// This can be called while the module timer is disabled,
|
||||
/// but the timer will not start until it is enabled.
|
||||
///
|
||||
/// Calling this while the timer is disabled can be useful
|
||||
/// if you want it to start counting right away.
|
||||
static void start_module_timer();
|
||||
|
||||
/// Stops the module timer.
|
||||
///
|
||||
/// This can be called while the module timer is disabled,
|
||||
/// but the timer only counts while it is enabled regardless.
|
||||
static void stop_module_timer();
|
||||
|
||||
/// Sets the module time.
|
||||
///
|
||||
/// This can be called even when the module timer is disabled.
|
||||
///
|
||||
/// A negative number will cause the timer to count up.
|
||||
static void set_module_time(int32_t millis);
|
||||
|
||||
/// Sets the game timer to the given raw segments.
|
||||
///
|
||||
/// You should ensure the game timer is disabled before
|
||||
/// calling this, otherwise, the data will be overwritten.
|
||||
static void set_game_raw(const std::array<uint8_t, 4>& segments);
|
||||
|
||||
/// Sets the game timer digit to the given raw segments.
|
||||
///
|
||||
/// You should ensure the game timer is disabled before
|
||||
/// calling this, otherwise, the data will be overwritten.
|
||||
///
|
||||
/// `digit` should be in the range 0..=3.
|
||||
static void set_game_digit_raw(uint8_t digit, uint8_t segments);
|
||||
|
||||
/// Sets the module timer to the given raw segments.
|
||||
///
|
||||
/// You should ensure the module timer is disabled before
|
||||
/// calling this, otherwise, the data will be overwritten.
|
||||
static void set_module_raw(const std::array<uint8_t, 4>& segments);
|
||||
|
||||
/// Sets the module timer digit to the given raw segments.
|
||||
///
|
||||
/// You should ensure the module timer is disabled before
|
||||
/// calling this, otherwise, the data will be overwritten.
|
||||
///
|
||||
/// `digit` should be in the range 0..=3.
|
||||
static void set_module_digit_raw(uint8_t digit, uint8_t segments);
|
||||
|
||||
/// Sets the rollover logic for the game timer.
|
||||
///
|
||||
/// If `true`, when the timer reaches zero, it will go
|
||||
/// negative, and start counting up.
|
||||
/// If `false`, when the timer reaches zero, it will stop
|
||||
/// the timer.
|
||||
static void game_timer_rollover(bool rollover);
|
||||
|
||||
/// Sets the rollover logic for the module timer.
|
||||
///
|
||||
/// If `true`, when the timer reaches zero, it will go
|
||||
/// negative, and start counting up.
|
||||
/// If `false`, when the timer reaches zero, it will stop
|
||||
/// the timer.
|
||||
static void module_timer_rollover(bool rollover);
|
||||
|
||||
/// Gets the current game time in millis.
|
||||
static int32_t get_game_time();
|
||||
|
||||
/// Gets the current module time in millis.
|
||||
static int32_t get_module_time();
|
||||
|
||||
/// Waits until all commands are flushed to the seven segments.
|
||||
static void flush();
|
||||
|
||||
/// Waits until the game timer is zero (or negative).
|
||||
static void wait_game_timer_done();
|
||||
|
||||
/// Waits until the module timer is zero (or negative).
|
||||
static void wait_module_timer_done();
|
||||
|
||||
/// Sets the intensity of the display.
|
||||
///
|
||||
/// `intensity` gets clamped to the range `0..=7`
|
||||
static void set_intensity(uint8_t intensity);
|
||||
|
||||
};
|
||||
|
||||
void init_ssegs();
|
||||
|
||||
#endif // SSEGS_HPP
|
||||
Loading…
Reference in New Issue
Block a user