Compare commits
8
Commits
8bc5a982d1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
562b6cea8b | ||
|
|
59d038efc2 | ||
|
|
0d761b048f | ||
|
|
30244c6d7b | ||
|
|
75052174a6 | ||
|
|
b3bcb4108d | ||
|
|
bd5a07924a | ||
|
|
0b79eb8399 |
@@ -1,9 +1,23 @@
|
|||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS "blk_box.cpp"
|
SRCS "blk_box.cpp"
|
||||||
INCLUDE_DIRS "include" "."
|
INCLUDE_DIRS "include" "."
|
||||||
|
REQUIRES
|
||||||
|
lvgl
|
||||||
PRIV_REQUIRES
|
PRIV_REQUIRES
|
||||||
|
led_strip
|
||||||
esp_driver_gpio
|
esp_driver_gpio
|
||||||
esp_driver_i2c
|
esp_driver_i2c
|
||||||
|
esp_driver_spi
|
||||||
|
esp_timer
|
||||||
|
esp_lcd
|
||||||
|
esp_event
|
||||||
|
esp_netif
|
||||||
|
esp_wifi
|
||||||
|
nvs_flash
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_options(${COMPONENT_LIB} PRIVATE
|
||||||
|
-Wno-missing-field-initializers
|
||||||
)
|
)
|
||||||
|
|
||||||
add_subdirectory(drivers)
|
add_subdirectory(drivers)
|
||||||
|
|||||||
+15
@@ -2,8 +2,23 @@
|
|||||||
|
|
||||||
#include "blk_box_drivers/i2c.h"
|
#include "blk_box_drivers/i2c.h"
|
||||||
#include "blk_box_drivers/inputs.hpp"
|
#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"
|
||||||
|
#include "blk_box_drivers/tft.hpp"
|
||||||
|
#include "blk_box_drivers/nvs.hpp"
|
||||||
|
#include "blk_box_drivers/radio.hpp"
|
||||||
|
#include "blk_box_drivers/bbnow.hpp"
|
||||||
|
|
||||||
void init_blk_box(BlkBoxInitConfig cfg) {
|
void init_blk_box(BlkBoxInitConfig cfg) {
|
||||||
init_main_i2c();
|
init_main_i2c();
|
||||||
init_expander();
|
init_expander();
|
||||||
|
init_leds();
|
||||||
|
init_lcd();
|
||||||
|
init_ssegs();
|
||||||
|
init_tft();
|
||||||
|
|
||||||
|
init_nvs();
|
||||||
|
init_radio();
|
||||||
|
init_espnow();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
set(SOURCES
|
set(SOURCES
|
||||||
|
"bbnow.cpp"
|
||||||
|
"char_lcd_headers.cpp"
|
||||||
|
"char_lcd.cpp"
|
||||||
|
"helpers.cpp"
|
||||||
"inputs.cpp"
|
"inputs.cpp"
|
||||||
"i2c.cpp"
|
"i2c.cpp"
|
||||||
|
"lcd2004.cpp"
|
||||||
|
"leds.cpp"
|
||||||
|
"nvs.cpp"
|
||||||
|
"radio.cpp"
|
||||||
|
"ssegs.cpp"
|
||||||
|
"tft.cpp"
|
||||||
|
"tm1640.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
target_sources(${COMPONENT_LIB} PRIVATE ${SOURCES})
|
target_sources(${COMPONENT_LIB} PRIVATE ${SOURCES})
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#include "blk_box_drivers/bbnow.hpp"
|
||||||
|
#include "esp_now.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_mac.h"
|
||||||
|
#include <memory.h>
|
||||||
|
|
||||||
|
const static char TAG[] = "bbnow";
|
||||||
|
|
||||||
|
static void example_espnow_send_cb(const esp_now_send_info_t *tx_info, esp_now_send_status_t status);
|
||||||
|
static void example_espnow_recv_cb(const esp_now_recv_info_t *recv_info, const uint8_t *data, int len);
|
||||||
|
|
||||||
|
/* ESPNOW sending or receiving callback function is called in WiFi task.
|
||||||
|
* Users should not do lengthy operations from this task. Instead, post
|
||||||
|
* necessary data to a queue and handle it from a lower priority task. */
|
||||||
|
static void example_espnow_send_cb(const esp_now_send_info_t *tx_info, esp_now_send_status_t status)
|
||||||
|
{
|
||||||
|
if (tx_info == NULL) {
|
||||||
|
ESP_LOGE(TAG, "Send cb arg error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "sending espnow packet of size: %d to " MACSTR, tx_info->data_len, MAC2STR(tx_info->des_addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void example_espnow_recv_cb(const esp_now_recv_info_t *recv_info, const uint8_t *data, int len)
|
||||||
|
{
|
||||||
|
uint8_t * src_addr = recv_info->src_addr;
|
||||||
|
uint8_t * des_addr = recv_info->des_addr;
|
||||||
|
|
||||||
|
if (src_addr == NULL || data == NULL || len <= 0) {
|
||||||
|
ESP_LOGE(TAG, "Receive cb arg error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "got espnow packet of size: %d from " MACSTR " to " MACSTR, MAC2STR(src_addr), MAC2STR(des_addr));
|
||||||
|
|
||||||
|
// if (IS_BROADCAST_ADDR(des_addr)) {
|
||||||
|
// /* If added a peer with encryption before, the receive packets may be
|
||||||
|
// * encrypted as peer-to-peer message or unencrypted over the broadcast channel.
|
||||||
|
// * Users can check the destination address to distinguish it.
|
||||||
|
// */
|
||||||
|
// ESP_LOGD(TAG, "Receive broadcast ESPNOW data");
|
||||||
|
// } else {
|
||||||
|
// ESP_LOGD(TAG, "Receive unicast ESPNOW data");
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: add "initializing _____..."
|
||||||
|
// and "____ initialized!" logs to all driver init functions.
|
||||||
|
void init_espnow(void) {
|
||||||
|
ESP_ERROR_CHECK( esp_now_init() );
|
||||||
|
ESP_ERROR_CHECK( esp_now_register_send_cb(example_espnow_send_cb) );
|
||||||
|
ESP_ERROR_CHECK( esp_now_register_recv_cb(example_espnow_recv_cb) );
|
||||||
|
|
||||||
|
/* Set primary master key. */
|
||||||
|
// TODO: add provisions for encryption
|
||||||
|
// ESP_ERROR_CHECK( esp_now_set_pmk(ESPNOW_PMK));
|
||||||
|
|
||||||
|
// Add broadcast peer information to peer list, allowing us to send to it
|
||||||
|
esp_now_peer_info_t peer = {0};
|
||||||
|
peer.channel = BBNOW_DEFAULT_CHANNEL;
|
||||||
|
peer.ifidx = WIFI_IF_AP;
|
||||||
|
peer.encrypt = false;
|
||||||
|
memcpy(peer.peer_addr, BROADCAST_MAC, ESP_NOW_ETH_ALEN);
|
||||||
|
ESP_ERROR_CHECK( esp_now_add_peer(&peer) );
|
||||||
|
}
|
||||||
@@ -0,0 +1,317 @@
|
|||||||
|
#include "blk_box_drivers/char_lcd.hpp"
|
||||||
|
|
||||||
|
#include "lcd2004.hpp"
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
|
||||||
|
// mutex is for all these vars
|
||||||
|
SemaphoreHandle_t lcd_mutex;
|
||||||
|
|
||||||
|
LCD2004I2C lcd;
|
||||||
|
|
||||||
|
static CursorMode cursor_resting_mode = CursorMode::Hide;
|
||||||
|
static CursorMode cursor_print_mode = CursorMode::Hide;
|
||||||
|
static uint8_t resting_cursor_row = 0;
|
||||||
|
static uint8_t resting_cursor_col = 0;
|
||||||
|
|
||||||
|
static bool is_header_enabled = false;
|
||||||
|
|
||||||
|
static const char *TAG = "char_lcd";
|
||||||
|
static const char* EMPTY_ROW = " ";
|
||||||
|
|
||||||
|
// TODO: move this to power.cpp
|
||||||
|
// static void monitor_battery_task(void* _arg) {
|
||||||
|
// (void) _arg;
|
||||||
|
|
||||||
|
// while (true) {
|
||||||
|
// vTaskDelay(pdMS_TO_TICKS(1'000));
|
||||||
|
// lcd_print_header_bat();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// static bool replay_handler(const char* event, char* arg) {
|
||||||
|
// if (strcmp(event, "LCD_CLEAR") == 0) {
|
||||||
|
// lcd_clear();
|
||||||
|
// }
|
||||||
|
// else if (strcmp(event, "LCD_SET_DISPLAY") == 0) {
|
||||||
|
// lcd_set_display(strcmp(arg, "true") == 0);
|
||||||
|
// }
|
||||||
|
// else if (strcmp(event, "LCD_CURSOR_VIS") == 0) {
|
||||||
|
// lcd_set_cursor_vis(strcmp(arg, "true") == 0);
|
||||||
|
// }
|
||||||
|
// else if (strcmp(event, "LCD_CURSOR_BLINK") == 0) {
|
||||||
|
// lcd_set_cursor_blink(strcmp(arg, "true") == 0);
|
||||||
|
// }
|
||||||
|
// else if (strcmp(event, "LCD_SCROLL_DISPLAY_LEFT") == 0) {
|
||||||
|
// lcd_scroll_display_left();
|
||||||
|
// }
|
||||||
|
// else if (strcmp(event, "LCD_SCROLL_DISPLAY_RIGHT") == 0) {
|
||||||
|
// lcd_scroll_display_right();
|
||||||
|
// }
|
||||||
|
// else if (strcmp(event, "LCD_LEFT_TO_RIGHT") == 0) {
|
||||||
|
// lcd_left_to_right();
|
||||||
|
// }
|
||||||
|
// else if (strcmp(event, "LCD_RIGHT_TO_LEFT") == 0) {
|
||||||
|
// lcd_right_to_left();
|
||||||
|
// }
|
||||||
|
// else if (strcmp(event, "LCD_AUTOSCROLL") == 0) {
|
||||||
|
// lcd_set_autoscroll(strcmp(arg, "true") == 0);
|
||||||
|
// }
|
||||||
|
// else if (strcmp(event, "LCD_BACKLIGHT") == 0) {
|
||||||
|
// lcd_set_backlight(strcmp(arg, "true") == 0);
|
||||||
|
// }
|
||||||
|
// else if (strcmp(event, "LCD_CREATE_CHAR") == 0) {
|
||||||
|
// char* location_str = strtok(arg, ",");
|
||||||
|
// uint8_t location = atoi(location_str);
|
||||||
|
|
||||||
|
// uint8_t charmap[8];
|
||||||
|
// for (int i = 0; i < 8; i++) {
|
||||||
|
// char* str = strtok(NULL, ",");
|
||||||
|
// charmap[i] = atoi(str);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// lcd_create_char(location, charmap);
|
||||||
|
// }
|
||||||
|
// else if (strcmp(event, "LCD_PRINT") == 0) {
|
||||||
|
// char* str = strtok(arg, ",");
|
||||||
|
// uint8_t row = atoi(str);
|
||||||
|
// str = strtok(NULL, ",");
|
||||||
|
// uint8_t col = atoi(str);
|
||||||
|
// // get remaining part of string.
|
||||||
|
// str = strtok(NULL, "");
|
||||||
|
|
||||||
|
// // TODO: handle \r and \n
|
||||||
|
// lcd_print(row, col, str);
|
||||||
|
// } else {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
void init_lcd() {
|
||||||
|
ESP_LOGI(TAG, "Initializing LCD...");
|
||||||
|
|
||||||
|
lcd_mutex = xSemaphoreCreateRecursiveMutex();
|
||||||
|
assert(lcd_mutex != NULL);
|
||||||
|
|
||||||
|
lcd = LCD2004I2C();
|
||||||
|
lcd.init(LCD_ADDR);
|
||||||
|
|
||||||
|
// register_replay_fn(replay_handler);
|
||||||
|
|
||||||
|
// xTaskCreate(monitor_battery_task, "bat_monitor", 1024*2, nullptr, 0, nullptr);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "LCD initialized!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCDController::clear() {
|
||||||
|
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
|
||||||
|
|
||||||
|
if (!is_header_enabled) {
|
||||||
|
lcd.clear();
|
||||||
|
|
||||||
|
// if (is_state_tracking()) {
|
||||||
|
// event_occured("LCD_CLEAR", NULL);
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
print(1, 0, EMPTY_ROW);
|
||||||
|
print(2, 0, EMPTY_ROW);
|
||||||
|
print(3, 0, EMPTY_ROW);
|
||||||
|
}
|
||||||
|
|
||||||
|
xSemaphoreGiveRecursive(lcd_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LCDController::get_backlight() {
|
||||||
|
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
|
||||||
|
bool backlight = lcd.get_backlight();
|
||||||
|
xSemaphoreGiveRecursive(lcd_mutex);
|
||||||
|
return backlight;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCDController::set_backlight(bool backlight) {
|
||||||
|
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
|
||||||
|
lcd.set_backlight(backlight);
|
||||||
|
xSemaphoreGiveRecursive(lcd_mutex);
|
||||||
|
|
||||||
|
// if (is_state_tracking()) {
|
||||||
|
// sprintf(buf, "%d", backlight);
|
||||||
|
// event_occured("LCD_BACKLIGHT", backlight ? "true" : "false");
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCDController::set_display_show(bool show_display) {
|
||||||
|
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
|
||||||
|
lcd.show_hide(show_display);
|
||||||
|
xSemaphoreGiveRecursive(lcd_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LCDController::get_display_show() {
|
||||||
|
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
|
||||||
|
bool show_display = lcd.get_show_hide();
|
||||||
|
xSemaphoreGiveRecursive(lcd_mutex);
|
||||||
|
return show_display;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes the cursor display mode to the given mode.
|
||||||
|
static void change_cursor_display(CursorMode cursor_display_mode) {
|
||||||
|
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
|
||||||
|
lcd.show_blink_cursor(
|
||||||
|
cursor_display_mode != CursorMode::Hide,
|
||||||
|
cursor_display_mode == CursorMode::Blink
|
||||||
|
);
|
||||||
|
xSemaphoreGiveRecursive(lcd_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves the position of the resting cursor.
|
||||||
|
///
|
||||||
|
/// If the resting cursor mode is `Hide`, then this position has no effect on the display, but is still stored
|
||||||
|
/// for when it is put into resting cursor mode.
|
||||||
|
void LCDController::set_resting_cursor_pos(uint8_t row, uint8_t col) {
|
||||||
|
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
|
||||||
|
|
||||||
|
// update pos if we're not hiding the resting cursor
|
||||||
|
if (
|
||||||
|
(resting_cursor_row != row || resting_cursor_col != col) &&
|
||||||
|
cursor_resting_mode != CursorMode::Hide
|
||||||
|
) {
|
||||||
|
lcd.move_cursor(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
resting_cursor_row = row;
|
||||||
|
resting_cursor_col = col;
|
||||||
|
xSemaphoreGiveRecursive(lcd_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the position of the resting cursor.
|
||||||
|
///
|
||||||
|
/// This will return the value of the resting cursor position even if the resting cursor mode is `Hide`
|
||||||
|
/// even though the values are not meaningful during that time.
|
||||||
|
void LCDController::get_cursor_resting_position(uint8_t* row, uint8_t* col) {
|
||||||
|
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
|
||||||
|
if (row) *row = resting_cursor_row;
|
||||||
|
if (col) *col = resting_cursor_col;
|
||||||
|
xSemaphoreGiveRecursive(lcd_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This puts the display in and out of resting cursor mode.
|
||||||
|
///
|
||||||
|
/// If the resting mode is not `Hide`, then the cursor will be displayed in the resting position when not printing.
|
||||||
|
///
|
||||||
|
/// The cursor mode will change to the "cursor print mode"
|
||||||
|
/// during prints, then return to it's resting location and
|
||||||
|
/// switch back to the "cursor resting mode".
|
||||||
|
void LCDController::set_resting_cursor_mode(CursorMode new_mode) {
|
||||||
|
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
|
||||||
|
if (cursor_resting_mode != new_mode) {
|
||||||
|
cursor_resting_mode = new_mode;
|
||||||
|
change_cursor_display(cursor_resting_mode);
|
||||||
|
if (cursor_resting_mode != CursorMode::Hide) {
|
||||||
|
lcd.move_cursor(resting_cursor_row, resting_cursor_col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xSemaphoreGiveRecursive(lcd_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the display mode of the cursor when it is resting.
|
||||||
|
CursorMode LCDController::get_resting_cursor_mode() {
|
||||||
|
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
|
||||||
|
CursorMode mode = cursor_resting_mode;
|
||||||
|
xSemaphoreGiveRecursive(lcd_mutex);
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the display mode of the cursor during printing.
|
||||||
|
void LCDController::set_cursor_print_mode(CursorMode new_mode) {
|
||||||
|
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
|
||||||
|
cursor_print_mode = new_mode;
|
||||||
|
xSemaphoreGiveRecursive(lcd_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the display mode of the cursor during printing.
|
||||||
|
CursorMode LCDController::get_cursor_print_mode() {
|
||||||
|
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
|
||||||
|
CursorMode mode = cursor_print_mode;
|
||||||
|
xSemaphoreGiveRecursive(lcd_mutex);
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCDController::create_custom_char(uint8_t location, const uint8_t charmap[]) {
|
||||||
|
if (location == 8) location = 0;
|
||||||
|
|
||||||
|
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
|
||||||
|
lcd.create_custom_char(location, charmap);
|
||||||
|
xSemaphoreGiveRecursive(lcd_mutex);
|
||||||
|
|
||||||
|
// if (is_state_tracking()) {
|
||||||
|
// snprintf(buf, sizeof(buf),
|
||||||
|
// "%d,%d,%d,%d,%d,%d,%d,%d,%d", location,
|
||||||
|
// charmap[0], charmap[1], charmap[2], charmap[3], charmap[4], charmap[5], charmap[6], charmap[7]
|
||||||
|
// );
|
||||||
|
// event_occured("LCD_CREATE_CHAR", buf);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints a string to the given row and column.
|
||||||
|
///
|
||||||
|
/// Do not print across lines, as that leads to goofy behavior.
|
||||||
|
void LCDController::print(uint8_t row, uint8_t col, const char* str) {
|
||||||
|
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
|
||||||
|
lcd.move_cursor(row, col);
|
||||||
|
change_cursor_display(cursor_print_mode);
|
||||||
|
lcd.write_str(str);
|
||||||
|
|
||||||
|
if (cursor_resting_mode != CursorMode::Hide) {
|
||||||
|
lcd.move_cursor(resting_cursor_row, resting_cursor_col);
|
||||||
|
}
|
||||||
|
if (cursor_resting_mode != cursor_print_mode) {
|
||||||
|
change_cursor_display(cursor_resting_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
xSemaphoreGiveRecursive(lcd_mutex);
|
||||||
|
|
||||||
|
// if (is_state_tracking()) {
|
||||||
|
// // TODO: handle \r and \n and others
|
||||||
|
// snprintf(buf, sizeof(buf), "%d,%d,%s", row, col, str);
|
||||||
|
// event_occured("LCD_PRINT", buf);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCDController::set_lcd_header_enabled(bool enable) {
|
||||||
|
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
|
||||||
|
bool old_header_enabled = is_header_enabled;
|
||||||
|
is_header_enabled = enable;
|
||||||
|
|
||||||
|
// print the header in response to enabling/disabling it
|
||||||
|
if (enable && !old_header_enabled) {
|
||||||
|
print_header();
|
||||||
|
} else if (!enable && old_header_enabled) {
|
||||||
|
print(0, 0, EMPTY_ROW);
|
||||||
|
}
|
||||||
|
xSemaphoreGiveRecursive(lcd_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LCDController::header_enabled() {
|
||||||
|
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
|
||||||
|
bool enabled = is_header_enabled;
|
||||||
|
xSemaphoreGiveRecursive(lcd_mutex);
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LCDController::lock(uint32_t ticks_to_wait) {
|
||||||
|
return xSemaphoreTakeRecursive(lcd_mutex, ticks_to_wait);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCDController::unlock() {
|
||||||
|
xSemaphoreGiveRecursive(lcd_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCDController::print_header() {
|
||||||
|
// TODO:
|
||||||
|
// lcd_print_header_star_code();
|
||||||
|
// lcd_print_header_step();
|
||||||
|
// lcd_print_header_bat();
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#include "blk_box_drivers/helpers.hpp"
|
||||||
|
#include "blk_box_drivers/char_lcd.hpp"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void lcd_do_splash() {
|
||||||
|
const uint8_t custom_char[6][8] = {
|
||||||
|
{ 0x01, 0x01, 0x02, 0x02, 0x07, 0x07, 0x0F, 0x0D },
|
||||||
|
{ 0x10, 0x10, 0x18, 0x18, 0x1C, 0x0C, 0x0E, 0x06 },
|
||||||
|
{ 0x00, 0x00, 0x01, 0x01, 0x03, 0x03, 0x07, 0x07 },
|
||||||
|
{ 0x19, 0x1B, 0x13, 0x17, 0x07, 0x0F, 0x0F, 0x1F },
|
||||||
|
{ 0x13, 0x1B, 0x1F, 0x1F, 0x00, 0x1F, 0x1F, 0x1F },
|
||||||
|
{ 0x00, 0x00, 0x10, 0x10, 0x00, 0x18, 0x1C, 0x1C },
|
||||||
|
};
|
||||||
|
|
||||||
|
LCDController::lock(portMAX_DELAY);
|
||||||
|
LCDController::create_custom_char(1, custom_char[0]);
|
||||||
|
LCDController::create_custom_char(2, custom_char[1]);
|
||||||
|
LCDController::create_custom_char(3, custom_char[2]);
|
||||||
|
LCDController::create_custom_char(4, custom_char[3]);
|
||||||
|
LCDController::create_custom_char(5, custom_char[4]);
|
||||||
|
LCDController::create_custom_char(6, custom_char[5]);
|
||||||
|
|
||||||
|
LCDController::print(1, 6, "\x01\x02Marino");
|
||||||
|
LCDController::print(2, 5, "\x03\x04\x05\x06""DEV");
|
||||||
|
LCDController::unlock();
|
||||||
|
}
|
||||||
@@ -0,0 +1,328 @@
|
|||||||
|
#include "lcd2004.hpp"
|
||||||
|
|
||||||
|
#include "pins.h"
|
||||||
|
#include "blk_box_drivers/i2c.h"
|
||||||
|
#include "driver/i2c_master.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_rom_sys.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
const static char* TAG = "LCD2004";
|
||||||
|
|
||||||
|
// masks
|
||||||
|
const uint8_t ENABLE_MASK = 0x04;
|
||||||
|
const uint8_t BACKLIGHT_MASK = 0x08;
|
||||||
|
const uint8_t DATA_MASK = 0x01;
|
||||||
|
|
||||||
|
const uint8_t CMD_CLEAR_DISPLAY = 0x01;
|
||||||
|
|
||||||
|
const uint8_t CMD_RETURN_HOME = 0x02;
|
||||||
|
|
||||||
|
const uint8_t CMD_ENTRY_MODE_SET = 0x04;
|
||||||
|
const uint8_t ENTRY_MODE_INC_DEC = 0x02;
|
||||||
|
const uint8_t ENTRY_MODE_SHIFT = 0x01;
|
||||||
|
|
||||||
|
const uint8_t CMD_DISPLAY_CONTROL = 0x08;
|
||||||
|
const uint8_t DISPLAY_CONTROL_SHOW = 0x04;
|
||||||
|
const uint8_t DISPLAY_CONTROL_CURSOR = 0x02;
|
||||||
|
const uint8_t DISPLAY_CONTROL_BLINK = 0x01;
|
||||||
|
|
||||||
|
const uint8_t CMD_CURSOR_OR_DISPLAY_SHIFT = 0x10;
|
||||||
|
const uint8_t CURSOR_OR_DISPLAY_SHIFT_SC = 0x08;
|
||||||
|
const uint8_t CURSOR_OR_DISPLAY_SHIFT_RL = 0x04;
|
||||||
|
|
||||||
|
const uint8_t CMD_FUNCTION_SET = 0x20;
|
||||||
|
const uint8_t FUNCTION_SET_DL = 0x10;
|
||||||
|
const uint8_t FUNCTION_SET_N = 0x08;
|
||||||
|
const uint8_t FUNCTION_SET_F = 0x04;
|
||||||
|
|
||||||
|
const uint8_t CMD_SET_CGRAM_ADDRESS = 0x40;
|
||||||
|
const uint8_t CMD_SET_DDRAM_ADDRESS = 0x80;
|
||||||
|
|
||||||
|
LCD2004I2C::LCD2004I2C() {
|
||||||
|
backlight = 0;
|
||||||
|
display_control = 0;
|
||||||
|
entry_mode = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::init(uint8_t addr) {
|
||||||
|
i2c_device_config_t dev_config = {
|
||||||
|
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||||
|
.device_address = addr,
|
||||||
|
.scl_speed_hz = LCD2004_CLK_SPEED_HZ,
|
||||||
|
.scl_wait_us = 0, // default
|
||||||
|
.flags = {
|
||||||
|
.disable_ack_check = 0,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: replace all these ESP_ERROR_CHECK with proper error handling that doesn't just crash the program
|
||||||
|
ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_main_bus_handle, &dev_config, &lcd_device_handle));
|
||||||
|
ESP_LOGD(TAG, "LCD2004 device added to bus");
|
||||||
|
|
||||||
|
// Initialize the LCD
|
||||||
|
// set to 8-bit mode to bring it to a known state
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
send4_cmd(CMD_FUNCTION_SET | FUNCTION_SET_DL);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
// set to 4-bit mode, while in 8 bit mode
|
||||||
|
send4_cmd(CMD_FUNCTION_SET);
|
||||||
|
|
||||||
|
// use all 4 rows
|
||||||
|
send_cmd(CMD_FUNCTION_SET | FUNCTION_SET_N);
|
||||||
|
|
||||||
|
// display, set cursor visible
|
||||||
|
send_cmd(CMD_DISPLAY_CONTROL | DISPLAY_CONTROL_SHOW);
|
||||||
|
|
||||||
|
// clear display
|
||||||
|
send_cmd(CMD_CLEAR_DISPLAY);
|
||||||
|
|
||||||
|
// set cursor to increment to the right
|
||||||
|
send_cmd(CMD_ENTRY_MODE_SET | ENTRY_MODE_INC_DEC);
|
||||||
|
|
||||||
|
display_control = DISPLAY_CONTROL_SHOW;
|
||||||
|
entry_mode = ENTRY_MODE_INC_DEC;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "LCD2004 initialized!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// i2c wrappers
|
||||||
|
|
||||||
|
/// Sends `cmd` to the device as a command.
|
||||||
|
void LCD2004I2C::send_cmd(uint8_t cmd) {
|
||||||
|
uint8_t control_bits = backlight;
|
||||||
|
|
||||||
|
uint8_t high = control_bits | (cmd & 0xF0);
|
||||||
|
uint8_t high_enable = high | ENABLE_MASK;
|
||||||
|
uint8_t low = control_bits | ((cmd << 4) & 0xF0);
|
||||||
|
uint8_t low_enable = low | ENABLE_MASK;
|
||||||
|
|
||||||
|
uint8_t i2c_data[] = {
|
||||||
|
high_enable,
|
||||||
|
high,
|
||||||
|
low_enable,
|
||||||
|
low
|
||||||
|
};
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(i2c_master_transmit(lcd_device_handle, i2c_data, sizeof(i2c_data), I2C_MASTER_TIMEOUT_MS));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::send_data(uint8_t data) {
|
||||||
|
uint8_t control_bits = DATA_MASK | backlight;
|
||||||
|
|
||||||
|
uint8_t high = control_bits | (data & 0xF0);
|
||||||
|
uint8_t high_enable = high | ENABLE_MASK;
|
||||||
|
uint8_t low = control_bits | ((data << 4) & 0xF0);
|
||||||
|
uint8_t low_enable = low | ENABLE_MASK;
|
||||||
|
|
||||||
|
// data pins are on pins D7 to D4
|
||||||
|
uint8_t i2c_data[] = {
|
||||||
|
high_enable,
|
||||||
|
high,
|
||||||
|
low_enable,
|
||||||
|
low
|
||||||
|
};
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(i2c_master_transmit(lcd_device_handle, i2c_data, sizeof(i2c_data), I2C_MASTER_TIMEOUT_MS));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the ***HIGH*** 4 bits of `cmd` as a command.
|
||||||
|
void LCD2004I2C::send4_cmd(uint8_t cmd) {
|
||||||
|
uint8_t control_bits = backlight;
|
||||||
|
|
||||||
|
uint8_t high = control_bits | (cmd & 0xF0);
|
||||||
|
uint8_t high_enable = high | ENABLE_MASK;
|
||||||
|
|
||||||
|
// data pins are on pins D7 to D4
|
||||||
|
uint8_t i2c_data[] = {
|
||||||
|
high_enable,
|
||||||
|
high
|
||||||
|
};
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(i2c_master_transmit(lcd_device_handle, i2c_data, sizeof(i2c_data), I2C_MASTER_TIMEOUT_MS));
|
||||||
|
}
|
||||||
|
|
||||||
|
// LCD2004 class implementations
|
||||||
|
|
||||||
|
bool LCD2004I2C::get_backlight() {
|
||||||
|
return backlight != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::set_backlight(bool backlight) {
|
||||||
|
if (get_backlight() == backlight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->backlight = backlight ? BACKLIGHT_MASK : 0;
|
||||||
|
uint8_t data = this->backlight;
|
||||||
|
ESP_ERROR_CHECK(i2c_master_transmit(lcd_device_handle, &data, 1, I2C_MASTER_TIMEOUT_MS));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::clear() {
|
||||||
|
send_cmd(CMD_CLEAR_DISPLAY);
|
||||||
|
esp_rom_delay_us(1600);
|
||||||
|
entry_mode |= ENTRY_MODE_INC_DEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::move_cursor(uint8_t row, uint8_t col) {
|
||||||
|
const uint8_t ROW_OFFSETS[4] = {0x00, 0x40, 0x14, 0x54};
|
||||||
|
uint8_t addr = ROW_OFFSETS[row] + col;
|
||||||
|
send_cmd(CMD_SET_DDRAM_ADDRESS | addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LCD2004I2C::get_show_hide() {
|
||||||
|
return (display_control & DISPLAY_CONTROL_SHOW) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::show_hide(bool show) {
|
||||||
|
if (get_show_hide() == show) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show) {
|
||||||
|
display_control |= DISPLAY_CONTROL_SHOW;
|
||||||
|
} else {
|
||||||
|
display_control &= ~DISPLAY_CONTROL_SHOW;
|
||||||
|
}
|
||||||
|
send_cmd(CMD_DISPLAY_CONTROL | display_control);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LCD2004I2C::get_show_cursor() {
|
||||||
|
return (display_control & DISPLAY_CONTROL_CURSOR) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::show_cursor(bool show_cursor) {
|
||||||
|
if (get_show_cursor() == show_cursor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_cursor) {
|
||||||
|
display_control |= DISPLAY_CONTROL_CURSOR;
|
||||||
|
} else {
|
||||||
|
display_control &= ~DISPLAY_CONTROL_CURSOR;
|
||||||
|
}
|
||||||
|
send_cmd(CMD_DISPLAY_CONTROL | display_control);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LCD2004I2C::get_blink_cursor() {
|
||||||
|
return (display_control & DISPLAY_CONTROL_BLINK) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::blink_cursor(bool blink_cursor) {
|
||||||
|
if (get_blink_cursor() == blink_cursor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blink_cursor) {
|
||||||
|
display_control |= DISPLAY_CONTROL_BLINK;
|
||||||
|
} else {
|
||||||
|
display_control &= ~DISPLAY_CONTROL_BLINK;
|
||||||
|
}
|
||||||
|
send_cmd(CMD_DISPLAY_CONTROL | display_control);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::show_blink_cursor(bool show_cursor, bool blink_cursor) {
|
||||||
|
if (get_show_cursor() == show_cursor && get_blink_cursor() == blink_cursor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blink_cursor) {
|
||||||
|
display_control |= DISPLAY_CONTROL_BLINK;
|
||||||
|
} else {
|
||||||
|
display_control &= ~DISPLAY_CONTROL_BLINK;
|
||||||
|
}
|
||||||
|
if (show_cursor) {
|
||||||
|
display_control |= DISPLAY_CONTROL_CURSOR;
|
||||||
|
} else {
|
||||||
|
display_control &= ~DISPLAY_CONTROL_CURSOR;
|
||||||
|
}
|
||||||
|
send_cmd(CMD_DISPLAY_CONTROL | display_control);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LCD2004I2C::get_scroll_direction() {
|
||||||
|
return (entry_mode & ENTRY_MODE_INC_DEC) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::scroll_direction(bool left_to_right) {
|
||||||
|
if (get_scroll_direction() == left_to_right) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left_to_right) {
|
||||||
|
entry_mode |= ENTRY_MODE_INC_DEC;
|
||||||
|
} else {
|
||||||
|
entry_mode &= ~ENTRY_MODE_INC_DEC;
|
||||||
|
}
|
||||||
|
send_cmd(CMD_ENTRY_MODE_SET | entry_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LCD2004I2C::get_display_shift() {
|
||||||
|
return (entry_mode & ENTRY_MODE_SHIFT) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::shift_display(bool shift_display) {
|
||||||
|
if (get_display_shift() == shift_display) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shift_display) {
|
||||||
|
entry_mode |= ENTRY_MODE_SHIFT;
|
||||||
|
} else {
|
||||||
|
entry_mode &= ~ENTRY_MODE_SHIFT;
|
||||||
|
}
|
||||||
|
send_cmd(CMD_ENTRY_MODE_SET | entry_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::shift_cursor_left() {
|
||||||
|
send_cmd(CMD_CURSOR_OR_DISPLAY_SHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::shift_cursor_right() {
|
||||||
|
send_cmd(CMD_CURSOR_OR_DISPLAY_SHIFT | CURSOR_OR_DISPLAY_SHIFT_RL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::shift_display_left() {
|
||||||
|
send_cmd(CMD_CURSOR_OR_DISPLAY_SHIFT | CURSOR_OR_DISPLAY_SHIFT_SC);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::shift_display_right() {
|
||||||
|
send_cmd(CMD_CURSOR_OR_DISPLAY_SHIFT | CURSOR_OR_DISPLAY_SHIFT_SC | CURSOR_OR_DISPLAY_SHIFT_RL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::create_custom_char(uint8_t location, const uint8_t char_map[8]) {
|
||||||
|
uint8_t loc = location % 8;
|
||||||
|
send_cmd(CMD_SET_CGRAM_ADDRESS | (loc << 3));
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
send_data(char_map[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::print_char(char c) {
|
||||||
|
if (c == '\x08') {
|
||||||
|
// custom char 0 is represented by \x08 since \x00 is a null terminator
|
||||||
|
c = 0;
|
||||||
|
}
|
||||||
|
send_data(static_cast<uint8_t>(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::print_u8(uint8_t b) {
|
||||||
|
if (b == 0x08) {
|
||||||
|
// custom char 0 is represented by \x08 since \x00 is a null terminator
|
||||||
|
b = 0;
|
||||||
|
}
|
||||||
|
send_data(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD2004I2C::write_str(const char* s) {
|
||||||
|
// TODO: replace this with a single I2C transmission.
|
||||||
|
while (*s) {
|
||||||
|
print_char(*s);
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
#ifndef LCD2004_HPP
|
||||||
|
#define LCD2004_HPP
|
||||||
|
|
||||||
|
#include "driver/i2c_master.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define LCD2004_CLK_SPEED_HZ 100000
|
||||||
|
|
||||||
|
#define I2C_MASTER_TIMEOUT_MS 1000
|
||||||
|
|
||||||
|
class LCD2004I2C {
|
||||||
|
private:
|
||||||
|
i2c_master_dev_handle_t lcd_device_handle;
|
||||||
|
/// Weather or not the backlight is on.
|
||||||
|
/// `BACKLIGHT_MASK` if backlight is on,
|
||||||
|
/// `0` if backlight is off.
|
||||||
|
uint8_t backlight;
|
||||||
|
/// The mask for the display control command.
|
||||||
|
uint8_t display_control;
|
||||||
|
uint8_t entry_mode;
|
||||||
|
|
||||||
|
void send_cmd(uint8_t cmd);
|
||||||
|
void send_data(uint8_t data);
|
||||||
|
void send4_cmd(uint8_t cmd);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructs a new LCD2004 device.
|
||||||
|
LCD2004I2C();
|
||||||
|
|
||||||
|
/// Initializes the LCD2004 device. Must be called before any other methods.
|
||||||
|
///
|
||||||
|
/// The address is usually `0x27`.
|
||||||
|
void init(uint8_t addr);
|
||||||
|
|
||||||
|
/// Gets the state of the backlight of the module.
|
||||||
|
bool get_backlight();
|
||||||
|
|
||||||
|
/// Sets the backlight of the module to be on or off.
|
||||||
|
void set_backlight(bool backlight);
|
||||||
|
|
||||||
|
/// Clears the display, and resets the cursor to
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/// Moves the cursor to `row` and `col`.
|
||||||
|
void move_cursor(uint8_t row, uint8_t col);
|
||||||
|
|
||||||
|
/// Returns `true` iff the display is showing.
|
||||||
|
bool get_show_hide();
|
||||||
|
|
||||||
|
/// Shows or hides the display.
|
||||||
|
void show_hide(bool show);
|
||||||
|
|
||||||
|
/// Returns `true` iff the cursor display is showing.
|
||||||
|
bool get_show_cursor();
|
||||||
|
|
||||||
|
/// Shows or hides the cursor.
|
||||||
|
void show_cursor(bool show_cursor);
|
||||||
|
|
||||||
|
/// Returns `true` iff the cursor is blinking.
|
||||||
|
///
|
||||||
|
/// This value is stored even if the cursor is not visible.
|
||||||
|
bool get_blink_cursor();
|
||||||
|
|
||||||
|
/// Blinks or stops blinking the cursor.
|
||||||
|
void blink_cursor(bool blink_cursor);
|
||||||
|
|
||||||
|
/// Sets the cursor show state and blink state.
|
||||||
|
void show_blink_cursor(bool show_cursor, bool blink_cursor);
|
||||||
|
|
||||||
|
/// Returns `true` iff the display scrolls left to right.
|
||||||
|
bool get_scroll_direction();
|
||||||
|
|
||||||
|
/// Sets the scroll direction for the display.
|
||||||
|
void scroll_direction(bool left_to_right);
|
||||||
|
|
||||||
|
/// Returns `true` iff the display is shifting with the cursor.
|
||||||
|
bool get_display_shift();
|
||||||
|
|
||||||
|
/// Sets the display to shift with the cursor.
|
||||||
|
///
|
||||||
|
/// This is like autoscrolling.
|
||||||
|
void shift_display(bool shift_display);
|
||||||
|
|
||||||
|
/// Shifts the cursor left 1
|
||||||
|
void shift_cursor_left();
|
||||||
|
|
||||||
|
/// Shifts the cursor right 1
|
||||||
|
void shift_cursor_right();
|
||||||
|
|
||||||
|
/// Shifts the display left 1
|
||||||
|
void shift_display_left();
|
||||||
|
|
||||||
|
/// Shifts the display right 1
|
||||||
|
void shift_display_right();
|
||||||
|
|
||||||
|
/// Sets the character map for a custom character.
|
||||||
|
///
|
||||||
|
/// There are 8 locations (0..=7), for 8 characters.
|
||||||
|
/// You can print these characters by sending '\x00' - '\x07'.
|
||||||
|
void create_custom_char(uint8_t location, const uint8_t char_map[8]);
|
||||||
|
|
||||||
|
/// Prints a single character to the display.
|
||||||
|
void print_char(char c);
|
||||||
|
|
||||||
|
/// Prints a single byte to the display.
|
||||||
|
void print_u8(uint8_t b);
|
||||||
|
|
||||||
|
/// Writes a string to the display.
|
||||||
|
void write_str(const char* s);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LCD2004_HPP
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
#include "blk_box_drivers/leds.hpp"
|
||||||
|
#include "pins.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "led_strip.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
|
||||||
|
const LEDColor LEDColor::OFF = LEDColor::from_rgb(0x00, 0x00, 0x00);
|
||||||
|
const LEDColor LEDColor::RED = LEDColor::from_rgb(0x17, 0x00, 0x00);
|
||||||
|
const LEDColor LEDColor::RED_STRONG = LEDColor::from_rgb(0xFF, 0x00, 0x00);
|
||||||
|
const LEDColor LEDColor::ORANGE = LEDColor::from_rgb(0x17, 0x02, 0x00);
|
||||||
|
const LEDColor LEDColor::ORANGE_STRONG = LEDColor::from_rgb(0xFF, 0x20, 0x00);
|
||||||
|
const LEDColor LEDColor::YELLOW = LEDColor::from_rgb(0x07, 0x07, 0x00);
|
||||||
|
const LEDColor LEDColor::YELLOW_STRONG = LEDColor::from_rgb(0xFF, 0xFF, 0x00);
|
||||||
|
const LEDColor LEDColor::GREEN = LEDColor::from_rgb(0x00, 0x07, 0x00);
|
||||||
|
const LEDColor LEDColor::GREEN_STRONG = LEDColor::from_rgb(0x00, 0xFF, 0x00);
|
||||||
|
const LEDColor LEDColor::BLUE = LEDColor::from_rgb(0x00, 0x00, 0x17);
|
||||||
|
const LEDColor LEDColor::BLUE_STRONG = LEDColor::from_rgb(0x00, 0x00, 0xFF);
|
||||||
|
const LEDColor LEDColor::PINK = LEDColor::from_rgb(0x10, 0x00, 0x04);
|
||||||
|
const LEDColor LEDColor::PINK_STRONG = LEDColor::from_rgb(0xFF, 0x00, 0x80);
|
||||||
|
const LEDColor LEDColor::WHITE = LEDColor::from_rgb(0x04, 0x04, 0x04);
|
||||||
|
const LEDColor LEDColor::WHITE_STRONG = LEDColor::from_rgb(0xFF, 0xFF, 0xFF);
|
||||||
|
|
||||||
|
const static char* TAG = "leds";
|
||||||
|
|
||||||
|
static led_strip_handle_t led_strip = NULL;
|
||||||
|
static SemaphoreHandle_t led_mutex = NULL;
|
||||||
|
|
||||||
|
void init_leds() {
|
||||||
|
led_mutex = xSemaphoreCreateMutex();
|
||||||
|
if (led_mutex == NULL) {
|
||||||
|
ESP_LOGE(TAG, "Failed to create LED mutex");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/// LED strip common configuration
|
||||||
|
led_strip_config_t strip_config = {
|
||||||
|
.strip_gpio_num = PIN_NEOPIXEL,
|
||||||
|
.max_leds = LED_COUNT,
|
||||||
|
// TODO: switch this over when we switch to the different LEDs
|
||||||
|
.led_model = LED_MODEL_WS2812,
|
||||||
|
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB,
|
||||||
|
.flags = {
|
||||||
|
.invert_out = false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// RMT backend specific configuration
|
||||||
|
led_strip_rmt_config_t rmt_config = {
|
||||||
|
.clk_src = RMT_CLK_SRC_DEFAULT,
|
||||||
|
.resolution_hz = LED_STRIP_RMT_RES_HZ,
|
||||||
|
.mem_block_symbols = RMT_LED_SYMBOLS * RMT_SYMBOLS_PER_LED,
|
||||||
|
.flags = {
|
||||||
|
.with_dma = false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Create the LED strip object
|
||||||
|
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(led_strip_clear(led_strip));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LEDController::set_led(uint32_t led, uint32_t color) {
|
||||||
|
xSemaphoreTake(led_mutex, portMAX_DELAY);
|
||||||
|
ESP_ERROR_CHECK(led_strip_set_pixel(led_strip, led, color >> 16 & 0xFF, color >> 8 & 0xFF, color & 0xFF));
|
||||||
|
xSemaphoreGive(led_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LEDController::flush() {
|
||||||
|
xSemaphoreTake(led_mutex, portMAX_DELAY);
|
||||||
|
ESP_ERROR_CHECK(led_strip_refresh(led_strip));
|
||||||
|
xSemaphoreGive(led_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LEDController::clear() {
|
||||||
|
xSemaphoreTake(led_mutex, portMAX_DELAY);
|
||||||
|
ESP_ERROR_CHECK(led_strip_clear(led_strip));
|
||||||
|
xSemaphoreGive(led_mutex);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#include "blk_box_drivers/nvs.hpp"
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
|
||||||
|
void init_nvs() {
|
||||||
|
// TODO: do more once we are doing more with nvs.
|
||||||
|
esp_err_t ret = nvs_flash_init();
|
||||||
|
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||||
|
// TODO: dont just erase, but also handle the case where we have to upgrade the nvs partition.
|
||||||
|
ESP_ERROR_CHECK( nvs_flash_erase() );
|
||||||
|
ret = nvs_flash_init();
|
||||||
|
}
|
||||||
|
ESP_ERROR_CHECK( ret );
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#include "blk_box_drivers/radio.hpp"
|
||||||
|
#include "blk_box_drivers/bbnow.hpp"
|
||||||
|
#include "esp_wifi.h"
|
||||||
|
|
||||||
|
void init_radio() {
|
||||||
|
// TODO: Do more once we are doing wifi in addition to espnow
|
||||||
|
ESP_ERROR_CHECK(esp_netif_init());
|
||||||
|
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||||
|
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||||
|
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
|
||||||
|
ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
|
||||||
|
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_AP) );
|
||||||
|
ESP_ERROR_CHECK( esp_wifi_start());
|
||||||
|
ESP_ERROR_CHECK( esp_wifi_set_channel(BBNOW_DEFAULT_CHANNEL, WIFI_SECOND_CHAN_NONE));
|
||||||
|
|
||||||
|
// enable long range
|
||||||
|
// ESP_ERROR_CHECK( esp_wifi_set_protocol(ESPNOW_WIFI_IF, WIFI_PROTOCOL_11B|WIFI_PROTOCOL_11G|WIFI_PROTOCOL_11N|WIFI_PROTOCOL_LR) );
|
||||||
|
}
|
||||||
@@ -0,0 +1,485 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: take timeout to these functions \/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
+249
@@ -0,0 +1,249 @@
|
|||||||
|
#include "blk_box_drivers/tft.hpp"
|
||||||
|
|
||||||
|
#include "pins.h"
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_lcd_panel_io.h>
|
||||||
|
#include <esp_lcd_panel_ops.h>
|
||||||
|
#include <esp_lcd_ili9488.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_timer.h>
|
||||||
|
|
||||||
|
static const char* TAG = "tft";
|
||||||
|
|
||||||
|
static esp_lcd_panel_io_handle_t lcd_io_handle = NULL;
|
||||||
|
static esp_lcd_panel_handle_t lcd_handle = NULL;
|
||||||
|
|
||||||
|
static lv_disp_draw_buf_t lv_disp_buf;
|
||||||
|
static lv_disp_drv_t lv_disp_drv;
|
||||||
|
static lv_disp_t *lv_display = NULL;
|
||||||
|
static lv_color_t *lv_buf_1 = NULL;
|
||||||
|
static lv_color_t *lv_buf_2 = NULL;
|
||||||
|
|
||||||
|
lv_obj_t* screen;
|
||||||
|
static lv_style_t style_screen;
|
||||||
|
|
||||||
|
SemaphoreHandle_t xGuiSemaphore;
|
||||||
|
|
||||||
|
// static bool replay_handler(const char* event, char* arg) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
static bool notify_lvgl_flush_ready(
|
||||||
|
esp_lcd_panel_io_handle_t panel_io,
|
||||||
|
esp_lcd_panel_io_event_data_t *edata,
|
||||||
|
void *user_ctx
|
||||||
|
) {
|
||||||
|
lv_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx;
|
||||||
|
lv_disp_flush_ready(disp_driver);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
// /// Base 64 encodes a u16... sort of. This doesn't do any of the fancy padding stuff.
|
||||||
|
// static void encode_base64(char* buf, size_t start_idx, uint16_t value) {
|
||||||
|
// buf[start_idx+0] = base64_chars[(value >> 10) & 0x3F];
|
||||||
|
// buf[start_idx+1] = base64_chars[(value >> 4) & 0x3F];
|
||||||
|
// buf[start_idx+2] = base64_chars[(value << 2) & 0x3F];
|
||||||
|
// }
|
||||||
|
|
||||||
|
static void lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) {
|
||||||
|
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;
|
||||||
|
|
||||||
|
int offsetx1 = area->x1;
|
||||||
|
int offsetx2 = area->x2;
|
||||||
|
int offsety1 = area->y1;
|
||||||
|
int offsety2 = area->y2;
|
||||||
|
|
||||||
|
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
|
||||||
|
|
||||||
|
// TODO: change this to be a kconfig value
|
||||||
|
#if false
|
||||||
|
|
||||||
|
if (is_state_tracking()) {
|
||||||
|
size_t size = (offsetx2 + 1 - offsetx1) * (offsety2 + 1 - offsety1) + 1;
|
||||||
|
// if (size > 1024) {
|
||||||
|
// ESP_LOGW("tft_track_state", "Write too big (%d)! truncating to 1024!", size);
|
||||||
|
// }
|
||||||
|
// size = MIN(1024, size);
|
||||||
|
|
||||||
|
|
||||||
|
// 24 bytes for the offsets
|
||||||
|
// 3 bytes per encoded color
|
||||||
|
// 1 byte for null terminator
|
||||||
|
size_t alloc_size = 24 + size * 3 + 1;
|
||||||
|
char* buf = (char*)malloc(alloc_size);
|
||||||
|
|
||||||
|
if (buf != nullptr) {
|
||||||
|
size_t initial_offset = sprintf(buf, "%d,%d,%d,%d:", offsetx1, offsety1, offsetx2 + 1, offsety2 + 1);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < size; i++) {
|
||||||
|
size_t index = initial_offset + i * 3;
|
||||||
|
|
||||||
|
// we assume that the size of the color data is 16b
|
||||||
|
static_assert(sizeof(lv_color_t) == sizeof(uint16_t), "lv_color_t must be 16b wide");
|
||||||
|
encode_base64(buf, index, color_map[i].full);
|
||||||
|
}
|
||||||
|
buf[initial_offset + (size-1) * 3 + 1] = '\0';
|
||||||
|
|
||||||
|
event_occured("TFT_W", buf);
|
||||||
|
free(buf);
|
||||||
|
} else {
|
||||||
|
ESP_LOGE("tft_track_state", "buffer alloc failed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void IRAM_ATTR lv_tick_task(void *param) {
|
||||||
|
lv_tick_inc(LVGL_UPDATE_PERIOD_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initialize_spi() {
|
||||||
|
ESP_LOGI(TAG, "Initializing SPI bus (MOSI:%d, MISO:%d, CLK:%d)",
|
||||||
|
PIN_TFT_MOSI, PIN_TFT_MISO, PIN_TFT_CLK);
|
||||||
|
|
||||||
|
spi_bus_config_t bus = {
|
||||||
|
.mosi_io_num = PIN_TFT_MOSI,
|
||||||
|
.miso_io_num = PIN_TFT_MISO,
|
||||||
|
.sclk_io_num = PIN_TFT_CLK,
|
||||||
|
.quadwp_io_num = GPIO_NUM_NC,
|
||||||
|
.quadhd_io_num = GPIO_NUM_NC,
|
||||||
|
.data4_io_num = GPIO_NUM_NC,
|
||||||
|
.data5_io_num = GPIO_NUM_NC,
|
||||||
|
.data6_io_num = GPIO_NUM_NC,
|
||||||
|
.data7_io_num = GPIO_NUM_NC,
|
||||||
|
.max_transfer_sz = SPI_MAX_TRANSFER_SIZE,
|
||||||
|
.flags = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MISO |
|
||||||
|
SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MASTER,
|
||||||
|
.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO,
|
||||||
|
.intr_flags = ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM
|
||||||
|
};
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &bus, SPI_DMA_CH_AUTO));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initialize_display() {
|
||||||
|
const esp_lcd_panel_io_spi_config_t io_config = {
|
||||||
|
.cs_gpio_num = PIN_TFT_CS,
|
||||||
|
.dc_gpio_num = PIN_TFT_RS,
|
||||||
|
.spi_mode = 0,
|
||||||
|
.pclk_hz = DISPLAY_REFRESH_HZ,
|
||||||
|
.trans_queue_depth = DISPLAY_SPI_QUEUE_LEN,
|
||||||
|
.on_color_trans_done = notify_lvgl_flush_ready,
|
||||||
|
.user_ctx = &lv_disp_drv,
|
||||||
|
.lcd_cmd_bits = DISPLAY_COMMAND_BITS,
|
||||||
|
.lcd_param_bits = DISPLAY_PARAMETER_BITS,
|
||||||
|
.flags = {
|
||||||
|
.dc_high_on_cmd = 0, /*!< If enabled, DC level = 1 indicates command transfer */
|
||||||
|
.dc_low_on_data = 0, /*!< If enabled, DC level = 0 indicates color data transfer */
|
||||||
|
.dc_low_on_param = 0, /*!< If enabled, DC level = 0 indicates parameter transfer */
|
||||||
|
.octal_mode = 0, /*!< transmit with octal mode (8 data lines), this mode is used to simulate Intel 8080 timing */
|
||||||
|
.quad_mode = 0, /*!< transmit with quad mode (4 data lines), this mode is useful when transmitting LCD parameters (Only use one line for command) */
|
||||||
|
.sio_mode = 0, /*!< Read and write through a single data line (MOSI) */
|
||||||
|
.lsb_first = 0, /*!< transmit LSB bit first */
|
||||||
|
.cs_high_active = 0, /*!< CS line is high active */
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const esp_lcd_panel_dev_config_t lcd_config = {
|
||||||
|
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
|
||||||
|
.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
|
||||||
|
.bits_per_pixel = 18,
|
||||||
|
.reset_gpio_num = PIN_TFT_RST,
|
||||||
|
.flags = {
|
||||||
|
.reset_active_high = 0
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI2_HOST, &io_config, &lcd_io_handle));
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9488_ips(lcd_io_handle, &lcd_config, LV_BUFFER_SIZE, &lcd_handle));
|
||||||
|
// ESP_ERROR_CHECK(esp_lcd_new_panel_ili9488(lcd_io_handle, &lcd_config, LV_BUFFER_SIZE, &lcd_handle));
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(esp_lcd_panel_reset(lcd_handle));
|
||||||
|
ESP_ERROR_CHECK(esp_lcd_panel_init(lcd_handle));
|
||||||
|
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(lcd_handle, true));
|
||||||
|
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(lcd_handle, true));
|
||||||
|
ESP_ERROR_CHECK(esp_lcd_panel_mirror(lcd_handle, false, true));
|
||||||
|
ESP_ERROR_CHECK(esp_lcd_panel_set_gap(lcd_handle, 0, 0));
|
||||||
|
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||||
|
ESP_ERROR_CHECK(esp_lcd_panel_disp_off(lcd_handle, false));
|
||||||
|
#else
|
||||||
|
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(lcd_handle, true));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void guiTask(void *pvParameter) {
|
||||||
|
xGuiSemaphore = xSemaphoreCreateRecursiveMutex();
|
||||||
|
ESP_LOGI(TAG, "Initializing LVGL");
|
||||||
|
lv_init();
|
||||||
|
ESP_LOGI(TAG, "Allocating %zu bytes for LVGL buffer", LV_BUFFER_SIZE * sizeof(lv_color_t));
|
||||||
|
lv_buf_1 = (lv_color_t *)heap_caps_malloc(LV_BUFFER_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
|
||||||
|
#if USE_DOUBLE_BUFFERING
|
||||||
|
ESP_LOGI(TAG, "Allocating %zu bytes for second LVGL buffer", LV_BUFFER_SIZE * sizeof(lv_color_t));
|
||||||
|
lv_buf_2 = (lv_color_t *)heap_caps_malloc(LV_BUFFER_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
|
||||||
|
#endif
|
||||||
|
ESP_LOGI(TAG, "Creating LVLG display buffer");
|
||||||
|
lv_disp_draw_buf_init(&lv_disp_buf, lv_buf_1, lv_buf_2, LV_BUFFER_SIZE);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Initializing %dx%d display", DISPLAY_HORIZONTAL_PIXELS, DISPLAY_VERTICAL_PIXELS);
|
||||||
|
lv_disp_drv_init(&lv_disp_drv);
|
||||||
|
lv_disp_drv.hor_res = DISPLAY_HORIZONTAL_PIXELS;
|
||||||
|
lv_disp_drv.ver_res = DISPLAY_VERTICAL_PIXELS;
|
||||||
|
lv_disp_drv.flush_cb = lvgl_flush_cb;
|
||||||
|
lv_disp_drv.draw_buf = &lv_disp_buf;
|
||||||
|
lv_disp_drv.user_data = lcd_handle;
|
||||||
|
// lv_disp_drv.rotated = LV_DISP_ROT_90;
|
||||||
|
lv_display = lv_disp_drv_register(&lv_disp_drv);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Creating LVGL tick timer");
|
||||||
|
const esp_timer_create_args_t lvgl_tick_timer_args = {
|
||||||
|
.callback = &lv_tick_task,
|
||||||
|
// .dispatch_method = ESP_TIMER_TASK,
|
||||||
|
.name = "periodic_gui",
|
||||||
|
// .skip_unhandled_events = false
|
||||||
|
};
|
||||||
|
esp_timer_handle_t periodic_timer;
|
||||||
|
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &periodic_timer));
|
||||||
|
ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, LVGL_UPDATE_PERIOD_MS * 1000));
|
||||||
|
|
||||||
|
screen = lv_scr_act();
|
||||||
|
lv_style_init(&style_screen);
|
||||||
|
lv_style_set_bg_color(&style_screen, lv_color_black());
|
||||||
|
lv_obj_add_style(screen, &style_screen, LV_STATE_DEFAULT);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
/* Delay 1 tick (assumes FreeRTOS tick is 10ms */
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
|
||||||
|
/* Try to take the semaphore, call lvgl related function on success */
|
||||||
|
if (pdTRUE == xSemaphoreTakeRecursive(xGuiSemaphore, portMAX_DELAY)) {
|
||||||
|
lv_task_handler();
|
||||||
|
xSemaphoreGiveRecursive(xGuiSemaphore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_tft() {
|
||||||
|
ESP_LOGI(TAG, "Initializing TFT...");
|
||||||
|
|
||||||
|
initialize_spi();
|
||||||
|
initialize_display();
|
||||||
|
xTaskCreatePinnedToCore(guiTask, "gui", 4096*2, NULL, 5, NULL, 1);
|
||||||
|
|
||||||
|
// register_replay_fn(replay_handler);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "TFT initialized!");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lvgl_lock(TickType_t ticks_to_wait) {
|
||||||
|
return xSemaphoreTakeRecursive(xGuiSemaphore, ticks_to_wait) == pdTRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lvgl_unlock() {
|
||||||
|
xSemaphoreGiveRecursive(xGuiSemaphore);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
+6
-13
@@ -2,17 +2,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
## Required IDF version
|
## Required IDF version
|
||||||
idf:
|
idf:
|
||||||
version: ">=6.0.0"
|
version: '>=6.0.0'
|
||||||
# # Put list of dependencies here
|
|
||||||
# # For components maintained by Espressif:
|
|
||||||
# component: "~1.0.0"
|
|
||||||
# # For 3rd party components:
|
|
||||||
# username/component: ">=1.0.0,<2.0.0"
|
|
||||||
# username2/component2:
|
|
||||||
# version: "~1.0.0"
|
|
||||||
# # For transient dependencies `public` flag can be set.
|
|
||||||
# # `public` flag doesn't have an effect dependencies of the `main` component.
|
|
||||||
# # All dependencies of `main` are public by default.
|
|
||||||
# public: true
|
|
||||||
espressif/led_strip: ^3.0.3
|
|
||||||
|
|
||||||
|
espressif/led_strip: ^3.0.3
|
||||||
|
# atanisoft/esp_lcd_ili9488: ^1.1.1
|
||||||
|
atanisoft/esp_lcd_ili9488:
|
||||||
|
path: ../../../esp_lcd_ili9488
|
||||||
|
lvgl/lvgl: ^8.4
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef BBNOW_HPP
|
||||||
|
#define BBNOW_HPP
|
||||||
|
|
||||||
|
#include "esp_now.h"
|
||||||
|
|
||||||
|
/// The channel to use if not on a WIFI network
|
||||||
|
#define BBNOW_DEFAULT_CHANNEL 6
|
||||||
|
const static uint8_t BROADCAST_MAC[ESP_NOW_ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||||
|
|
||||||
|
void init_espnow();
|
||||||
|
|
||||||
|
#endif /* BBNOW_HPP */
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#ifndef CHAR_LCD_HPP
|
||||||
|
#define CHAR_LCD_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#define LCD_ADDR 0x27
|
||||||
|
#define LCD_ROWS 4
|
||||||
|
#define LCD_COLS 20
|
||||||
|
|
||||||
|
/// A possible display mode for the cursor.
|
||||||
|
enum class CursorMode: uint8_t {
|
||||||
|
/// The cursor is not displayed.
|
||||||
|
Hide = 0b00,
|
||||||
|
/// The cursor is displayed as an underline.
|
||||||
|
Show = 0b01,
|
||||||
|
/// The cursor is displayed as a blinking block.
|
||||||
|
Blink = 0b11,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Initializes the 2004 Character LCD
|
||||||
|
void init_lcd();
|
||||||
|
|
||||||
|
class LCDController {
|
||||||
|
public:
|
||||||
|
/// Clears the display and resets the cursor to the home position (0, 0).
|
||||||
|
static void clear();
|
||||||
|
|
||||||
|
/// Gets the backlight state of the LCD.
|
||||||
|
static bool get_backlight();
|
||||||
|
/// Sets the backlight state of the LCD.
|
||||||
|
static void set_backlight(bool backlight);
|
||||||
|
|
||||||
|
/// Shows or hides the display.
|
||||||
|
static void set_display_show(bool show_display);
|
||||||
|
|
||||||
|
/// Gets the display state of the LCD.
|
||||||
|
static bool get_display_show();
|
||||||
|
|
||||||
|
/// Moves the position of the resting cursor.
|
||||||
|
///
|
||||||
|
/// If the resting cursor mode is `Hide`, then this position has no effect on the display, but is still stored
|
||||||
|
/// for when it is put into resting cursor mode.
|
||||||
|
static void set_resting_cursor_pos(uint8_t row, uint8_t col);
|
||||||
|
|
||||||
|
/// Gets the position of the resting cursor.
|
||||||
|
///
|
||||||
|
/// This will return the value of the resting cursor position even if the resting cursor mode is `Hide`
|
||||||
|
/// even though the values are not meaningful during that time.
|
||||||
|
static void get_cursor_resting_position(uint8_t* row, uint8_t* col);
|
||||||
|
|
||||||
|
/// This puts the display in and out of resting cursor mode.
|
||||||
|
///
|
||||||
|
/// If the resting mode is not `Hide`, then the cursor will be displayed in the resting position when not printing.
|
||||||
|
///
|
||||||
|
/// The cursor mode will change to the "cursor print mode"
|
||||||
|
/// during prints, then return to it's resting location and
|
||||||
|
/// switch back to the "cursor resting mode".
|
||||||
|
static void set_resting_cursor_mode(CursorMode new_mode);
|
||||||
|
|
||||||
|
/// Gets the display mode of the cursor when it is resting.
|
||||||
|
static CursorMode get_resting_cursor_mode();
|
||||||
|
|
||||||
|
/// Sets the display mode of the cursor during printing.
|
||||||
|
static void set_cursor_print_mode(CursorMode new_mode);
|
||||||
|
|
||||||
|
/// Gets the display mode of the cursor during printing.
|
||||||
|
static CursorMode get_cursor_print_mode();
|
||||||
|
|
||||||
|
/// Sets the character map for a custom character.
|
||||||
|
///
|
||||||
|
/// There are 8 locations (1..=8), for 8 characters.
|
||||||
|
/// You can print these characters by sending '\x01' - '\x08'.
|
||||||
|
static void create_custom_char(uint8_t location, const uint8_t charmap[]);
|
||||||
|
|
||||||
|
/// Prints a string to the given row and column.
|
||||||
|
///
|
||||||
|
/// Do not print across lines, as that leads to goofy behavior.
|
||||||
|
static void print(uint8_t row, uint8_t col, const char* str);
|
||||||
|
|
||||||
|
/// Enables or disables the header row.
|
||||||
|
///
|
||||||
|
/// Try to keep it enabled, since its used as a status display and shows the starcode the user is typing.
|
||||||
|
///
|
||||||
|
/// But can be disabled if needed.
|
||||||
|
static void set_lcd_header_enabled(bool enable);
|
||||||
|
|
||||||
|
/// Gets whether the header row is enabled.
|
||||||
|
static bool header_enabled();
|
||||||
|
|
||||||
|
/// Prints the LCD header. Usually, you will not need to call this manually.
|
||||||
|
static void print_header();
|
||||||
|
|
||||||
|
/// Locks the LCD to allow you to perform multiple operations uninterrupted.
|
||||||
|
///
|
||||||
|
/// Every lock should have an unlock. Do not hold the lock for too long.
|
||||||
|
///
|
||||||
|
/// This will wait up to `ticks_to_wait` ticks to acquire the lock, and return `false` if it fails to acquire the lock within that time.
|
||||||
|
static bool lock(uint32_t ticks_to_wait);
|
||||||
|
|
||||||
|
/// Unlocks the LCD after a lock.
|
||||||
|
static void unlock();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* CHAR_LCD_HPP */
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#ifndef HELPERS_HPP
|
||||||
|
#define HELPERS_HPP
|
||||||
|
|
||||||
|
// Does the MarinoDev splash screen.
|
||||||
|
void lcd_do_splash();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif // HELPERS_HPP
|
||||||
@@ -26,10 +26,16 @@ enum class Button: uint8_t {
|
|||||||
B2 = 1,
|
B2 = 1,
|
||||||
B3 = 2,
|
B3 = 2,
|
||||||
B4 = 3,
|
B4 = 3,
|
||||||
|
|
||||||
GREEN = 0,
|
GREEN = 0,
|
||||||
YELLOW = 1,
|
RED = 1,
|
||||||
RED = 2,
|
YELLOW = 2,
|
||||||
BLUE = 3,
|
BLUE = 3,
|
||||||
|
|
||||||
|
LEFT = 0,
|
||||||
|
DOWN = 1,
|
||||||
|
UP = 2,
|
||||||
|
RIGHT = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr uint8_t raw_value(Button v) { return static_cast<uint8_t>(v); }
|
constexpr uint8_t raw_value(Button v) { return static_cast<uint8_t>(v); }
|
||||||
@@ -85,7 +91,6 @@ constexpr char keypad_key_to_char(KeypadKey key) {
|
|||||||
return lookup[static_cast<uint8_t>(key) & 0b1111];
|
return lookup[static_cast<uint8_t>(key) & 0b1111];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct SwitchFlip {
|
struct SwitchFlip {
|
||||||
private:
|
private:
|
||||||
// [bit2: up] [bit1-0: switch]
|
// [bit2: up] [bit1-0: switch]
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
#ifndef LEDS_H
|
||||||
|
#define LEDS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
const uint32_t LED_COUNT = 21;
|
||||||
|
const uint32_t LED_SHAPE_COUNT = 4;
|
||||||
|
const uint32_t LED_INDICATOR_COUNT = 17;
|
||||||
|
|
||||||
|
// We will store 7 LEDs worth of data in the RMT peripheral,
|
||||||
|
// so we can update all the LEDs in 3 transactions.
|
||||||
|
#define RMT_LED_SYMBOLS 7
|
||||||
|
// Each LED requires 24 RMT symbols (1 for each bit)
|
||||||
|
#define RMT_SYMBOLS_PER_LED 24
|
||||||
|
|
||||||
|
// 10MHz resolution, 1 tick = 0.1us (led strip needs a high resolution)
|
||||||
|
#define LED_STRIP_RMT_RES_HZ (10 * 1000 * 1000)
|
||||||
|
|
||||||
|
class LEDColor {
|
||||||
|
private:
|
||||||
|
uint32_t color;
|
||||||
|
constexpr LEDColor(uint32_t color) : color(color) {}
|
||||||
|
public:
|
||||||
|
constexpr LEDColor(uint8_t r, uint8_t g, uint8_t b)
|
||||||
|
: color((static_cast<uint32_t>(r) << 16)
|
||||||
|
| (static_cast<uint32_t>(g) << 8)
|
||||||
|
| static_cast<uint32_t>(b)) {}
|
||||||
|
|
||||||
|
constexpr static LEDColor from_rgb(uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
return LEDColor(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const LEDColor OFF;
|
||||||
|
static const LEDColor RED;
|
||||||
|
static const LEDColor RED_STRONG;
|
||||||
|
static const LEDColor ORANGE;
|
||||||
|
static const LEDColor ORANGE_STRONG;
|
||||||
|
static const LEDColor YELLOW;
|
||||||
|
static const LEDColor YELLOW_STRONG;
|
||||||
|
static const LEDColor GREEN;
|
||||||
|
static const LEDColor GREEN_STRONG;
|
||||||
|
static const LEDColor BLUE;
|
||||||
|
static const LEDColor BLUE_STRONG;
|
||||||
|
static const LEDColor PINK;
|
||||||
|
static const LEDColor PINK_STRONG;
|
||||||
|
static const LEDColor WHITE;
|
||||||
|
static const LEDColor WHITE_STRONG;
|
||||||
|
|
||||||
|
inline constexpr uint32_t value() const { return color; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/// One of the shapes on the shape display.
|
||||||
|
enum class ShapeLed {
|
||||||
|
Shape1Led = 0,
|
||||||
|
Shape2Led = 1,
|
||||||
|
Shape3Led = 2,
|
||||||
|
Shape4Led = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// One of the indicator LEDs.
|
||||||
|
enum class IndicatorLED {
|
||||||
|
MODULE_SSEG = 4,
|
||||||
|
GAME_SSEG = 5,
|
||||||
|
TFT = 6,
|
||||||
|
MIC = 7,
|
||||||
|
IR_LED = 8,
|
||||||
|
SPEAKER = 9,
|
||||||
|
RFID = 10,
|
||||||
|
KEYPAD = 11,
|
||||||
|
LCD = 12,
|
||||||
|
S4 = 13,
|
||||||
|
S3 = 14,
|
||||||
|
S2 = 15,
|
||||||
|
S1 = 16,
|
||||||
|
B4 = 17,
|
||||||
|
B3 = 18,
|
||||||
|
B2 = 19,
|
||||||
|
B1 = 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Initializes the indicator LEDs
|
||||||
|
void init_leds();
|
||||||
|
|
||||||
|
class LEDController {
|
||||||
|
private:
|
||||||
|
static void set_led(uint32_t led, uint32_t color);
|
||||||
|
public:
|
||||||
|
/// Sets the color of an indicator LED.
|
||||||
|
///
|
||||||
|
/// Call `flush()` to send the data to the LEDs.
|
||||||
|
static void set_indicator(IndicatorLED led, LEDColor color) {
|
||||||
|
set_led(static_cast<uint32_t>(led), color.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the color of an indicator LED.
|
||||||
|
///
|
||||||
|
/// Call `flush()` to send the data to the LEDs.
|
||||||
|
static void set_shape(ShapeLed led, LEDColor color) {
|
||||||
|
set_led(static_cast<uint32_t>(led), color.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Outputs the data to the leds.
|
||||||
|
static void flush();
|
||||||
|
|
||||||
|
/// Clears the LEDs
|
||||||
|
static void clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // LEDS_H
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#ifndef NVS_HPP
|
||||||
|
#define NVS_HPP
|
||||||
|
|
||||||
|
void init_nvs();
|
||||||
|
|
||||||
|
#endif /* NVS_HPP */
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#ifndef RADIO_HPP
|
||||||
|
#define RADIO_HPP
|
||||||
|
|
||||||
|
void init_radio();
|
||||||
|
|
||||||
|
#endif /* RADIO_HPP */
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#ifndef TFT_HPP
|
||||||
|
#define TFT_HPP
|
||||||
|
|
||||||
|
#include <lvgl.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
|
||||||
|
// Uncomment the following line to enable using double buffering of LVGL color
|
||||||
|
// data.
|
||||||
|
// #define USE_DOUBLE_BUFFERING 1
|
||||||
|
|
||||||
|
// rotation swaps the horizontal and vertical pixel counts
|
||||||
|
#define DISPLAY_HORIZONTAL_PIXELS 480
|
||||||
|
#define DISPLAY_VERTICAL_PIXELS 320
|
||||||
|
#define DISPLAY_COMMAND_BITS 8
|
||||||
|
#define DISPLAY_PARAMETER_BITS 8
|
||||||
|
#define DISPLAY_REFRESH_HZ 40000000
|
||||||
|
#define DISPLAY_SPI_QUEUE_LEN 10
|
||||||
|
#define SPI_MAX_TRANSFER_SIZE 32768
|
||||||
|
|
||||||
|
#define TFT_INVERT_COLOR false
|
||||||
|
|
||||||
|
// Default to 50 lines of color data
|
||||||
|
#define LV_BUFFER_SIZE DISPLAY_HORIZONTAL_PIXELS * 50
|
||||||
|
#define LVGL_UPDATE_PERIOD_MS 5
|
||||||
|
|
||||||
|
#define BACKLIGHT_LEDC_MODE LEDC_LOW_SPEED_MODE
|
||||||
|
#define BACKLIGHT_LEDC_CHANNEL LEDC_CHANNEL_0
|
||||||
|
#define BACKLIGHT_LEDC_TIMER LEDC_TIMER_1
|
||||||
|
#define BACKLIGHT_LEDC_TIMER_RESOLUTION LEDC_TIMER_10_BIT
|
||||||
|
#define BACKLIGHT_LEDC_FRQUENCY 5000
|
||||||
|
|
||||||
|
extern lv_obj_t* screen;
|
||||||
|
|
||||||
|
void init_tft();
|
||||||
|
|
||||||
|
bool lvgl_lock(TickType_t ticks_to_wait);
|
||||||
|
void lvgl_unlock();
|
||||||
|
|
||||||
|
#endif // TFT_HPP
|
||||||
+8
-6
@@ -6,11 +6,12 @@
|
|||||||
#define PIN_SDA (GPIO_NUM_7)
|
#define PIN_SDA (GPIO_NUM_7)
|
||||||
#define PIN_SCL (GPIO_NUM_15)
|
#define PIN_SCL (GPIO_NUM_15)
|
||||||
|
|
||||||
#define PIN_LCD_MISO (GPIO_NUM_16)
|
#define PIN_TFT_CS (GPIO_NUM_NC)
|
||||||
#define PIN_LCD_MOSI (GPIO_NUM_17)
|
#define PIN_TFT_MISO (GPIO_NUM_16)
|
||||||
#define PIN_LCD_CLK (GPIO_NUM_18)
|
#define PIN_TFT_MOSI (GPIO_NUM_17)
|
||||||
#define PIN_LCD_RS (GPIO_NUM_8)
|
#define PIN_TFT_CLK (GPIO_NUM_18)
|
||||||
#define PIN_LCD_RST (GPIO_NUM_9)
|
#define PIN_TFT_RS (GPIO_NUM_8)
|
||||||
|
#define PIN_TFT_RST (GPIO_NUM_9)
|
||||||
|
|
||||||
#define PIN_USB_DM (GPIO_NUM_19)
|
#define PIN_USB_DM (GPIO_NUM_19)
|
||||||
#define PIN_USB_DP (GPIO_NUM_20)
|
#define PIN_USB_DP (GPIO_NUM_20)
|
||||||
@@ -27,7 +28,8 @@
|
|||||||
|
|
||||||
#define PIN_IR_RCV (GPIO_NUM_14)
|
#define PIN_IR_RCV (GPIO_NUM_14)
|
||||||
|
|
||||||
#define PIN_NEOPIXEL (GPIO_NUM_21)
|
// #define PIN_NEOPIXEL (GPIO_NUM_21) // Rev 2.1
|
||||||
|
#define PIN_NEOPIXEL (GPIO_NUM_0) // Rev 2.0
|
||||||
|
|
||||||
#define PIN_SD_DAT0 (GPIO_NUM_38)
|
#define PIN_SD_DAT0 (GPIO_NUM_38)
|
||||||
#define PIN_SD_DAT1 (GPIO_NUM_47)
|
#define PIN_SD_DAT1 (GPIO_NUM_47)
|
||||||
|
|||||||
Reference in New Issue
Block a user