Merge develop into main branch (#137)

* SdCard HAL refactored (#135)

- Refactor SdCard HAL
- introduce Lockable

* Screenshot and FatFS improvements (#136)

- Fix screenshots on ESP32
- Improve Screenshot service
- Convert Screenshot app to class-based instead of structs
- Screenshot app now automatically updates when task is finished
- Enable FatFS long filename support

* Re-use common log messages (#138)

For consistency and binary size reduction

* Toolbar spinner should get margin to the right

* More TactilityC features (#139)

* Rewrote Loader

- Simplified Loader by removing custom threa
- Created DispatcherThread
- Move auto-starting apps to Boot app
- Fixed Dispatcher bug where it could get stuck not processing new
messages

* Hide AP settings if the AP is not saved

* Missing from previous commit

* Replace LV_EVENT_CLICKED with LV_EVENT_SHORT_CLICKED

* Refactored files app and created InputDialog (#140)

- Changed Files app so that it has a View and State
- Files app now allows for long-pressing on files to perform actions
- Files app now has rename and delete actions
- Created InputDialog app
- Improved AlertDialog app layout
This commit is contained in:
Ken Van Hoeylandt
2024-12-27 22:12:39 +00:00
committed by GitHub
parent 9033daa6dd
commit 50bd6e8bf6
144 changed files with 3244 additions and 2038 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
#pragma once
#include "Power.h"
#include "hal/sdcard/Sdcard.h"
#include "hal/i2c/I2c.h"
#include "SdCard.h"
namespace tt::hal {
@@ -49,7 +49,7 @@ struct Configuration {
/**
* An optional SD card interface.
*/
const sdcard::SdCard* _Nullable sdcard = nullptr;
const std::shared_ptr<SdCard> _Nullable sdcard = nullptr;
/**
* An optional power interface for battery or other power delivery.
+1 -1
View File
@@ -19,7 +19,7 @@ void init(const Configuration& configuration) {
if (configuration.sdcard != nullptr) {
TT_LOG_I(TAG, "Mounting sdcard");
if (!sdcard::mount(configuration.sdcard)) {
if (!configuration.sdcard->mount(TT_SDCARD_MOUNT_POINT )) {
TT_LOG_W(TAG, "SD card mount failed (init can continue)");
}
}
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include "TactilityCore.h"
namespace tt::hal {
#define TT_SDCARD_MOUNT_POINT "/sdcard"
class SdCard {
public:
enum State {
StateMounted,
StateUnmounted,
StateError,
StateUnknown
};
enum MountBehaviour {
MountBehaviourAtBoot, /** Only mount at boot */
MountBehaviourAnytime /** Mount/dismount any time */
};
private:
MountBehaviour mountBehaviour;
public:
explicit SdCard(MountBehaviour mountBehaviour) : mountBehaviour(mountBehaviour) {}
virtual ~SdCard() = default;
virtual bool mount(const char* mountPath) = 0;
virtual bool unmount() = 0;
virtual State getState() const = 0;
virtual MountBehaviour getMountBehaviour() const { return mountBehaviour; }
bool isMounted() const { return getState() == StateMounted; }
};
} // namespace
+159
View File
@@ -0,0 +1,159 @@
#ifdef ESP_PLATFORM
#include "SpiSdCard.h"
#include "Check.h"
#include "Log.h"
#include <driver/gpio.h>
#include <esp_vfs_fat.h>
#include <sdmmc_cmd.h>
#define TAG "spi_sdcard"
namespace tt::hal {
/**
* Before we can initialize the sdcard's SPI communications, we have to set all
* other SPI pins on the board high.
* See https://github.com/espressif/esp-idf/issues/1597
* See https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/examples/UnitTest/UnitTest.ino
* @return success result
*/
bool SpiSdCard::applyGpioWorkAround() {
TT_LOG_D(TAG, "init");
uint64_t pin_bit_mask = BIT64(config->spiPinCs);
for (auto const& pin: config->csPinWorkAround) {
pin_bit_mask |= BIT64(pin);
}
gpio_config_t sd_gpio_config = {
.pin_bit_mask = pin_bit_mask,
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
if (gpio_config(&sd_gpio_config) != ESP_OK) {
TT_LOG_E(TAG, "GPIO init failed");
return false;
}
for (auto const& pin: config->csPinWorkAround) {
if (gpio_set_level(pin, 1) != ESP_OK) {
TT_LOG_E(TAG, "Failed to set board CS pin high");
return false;
}
}
return true;
}
bool SpiSdCard::mountInternal(const char* mountPoint) {
TT_LOG_I(TAG, "Mounting %s", mountPoint);
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = config->formatOnMountFailed,
.max_files = config->maxOpenFiles,
.allocation_unit_size = config->allocUnitSize,
.disk_status_check_enable = config->statusCheckEnabled,
.use_one_fat = false
};
// Init without card detect (CD) and write protect (WD)
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
slot_config.gpio_cs = config->spiPinCs;
slot_config.gpio_cd = config->spiPinCd;
slot_config.gpio_wp = config->spiPinWp;
slot_config.gpio_int = config->spiPinInt;
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
// The following value is from T-Deck repo's UnitTest.ino project:
// https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/examples/UnitTest/UnitTest.ino
// Observation: Using this automatically sets the bus to 20MHz
host.max_freq_khz = config->spiFrequency;
esp_err_t result = esp_vfs_fat_sdspi_mount(mountPoint, &host, &slot_config, &mount_config, &card);
if (result != ESP_OK) {
if (result == ESP_FAIL) {
TT_LOG_E(TAG, "Mounting failed. Ensure the card is formatted with FAT.");
} else {
TT_LOG_E(TAG, "Mounting failed (%s)", esp_err_to_name(result));
}
return false;
}
this->mountPoint = mountPoint;
return true;
}
bool SpiSdCard::mount(const char* mount_point) {
if (!applyGpioWorkAround()) {
TT_LOG_E(TAG, "Failed to set SPI CS pins high. This is a pre-requisite for mounting.");
return false;
}
if (mountInternal(mount_point)) {
sdmmc_card_print_info(stdout, card);
return true;
} else {
TT_LOG_E(TAG, "Mount failed for %s", mount_point);
return false;
}
}
bool SpiSdCard::unmount() {
if (card == nullptr) {
TT_LOG_E(TAG, "Can't unmount: not mounted");
return false;
}
if (esp_vfs_fat_sdcard_unmount(mountPoint.c_str(), card) == ESP_OK) {
mountPoint = "";
card = nullptr;
return true;
} else {
TT_LOG_E(TAG, "Unmount failed for %s", mountPoint.c_str());
return false;
}
}
// TODO: Refactor to "bool getStatus(Status* status)" method so that it can fail when the lvgl lock fails
tt::hal::SdCard::State SpiSdCard::getState() const {
if (card == nullptr) {
return StateUnmounted;
}
/**
* The SD card and the screen are on the same SPI bus.
* Writing and reading to the bus from 2 devices at the same time causes crashes.
* This work-around ensures that this check is only happening when LVGL isn't rendering.
*/
if (config->lockable) {
bool locked = config->lockable->lock(50); // TODO: Refactor to a more reliable locking mechanism
if (!locked) {
TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "LVGL");
return StateUnknown;
}
}
bool result = sdmmc_get_status(card) == ESP_OK;
if (config->lockable) {
config->lockable->unlock();
}
if (result) {
return StateMounted;
} else {
return StateError;
}
}
}
#endif
+80
View File
@@ -0,0 +1,80 @@
#ifdef ESP_PLATFORM
#pragma once
#include "hal/SdCard.h"
#include <sd_protocol_types.h>
#include <utility>
#include <vector>
#include <hal/spi_types.h>
#include <soc/gpio_num.h>
namespace tt::hal {
/**
* SD card interface at the default SPI interface
*/
class SpiSdCard : public tt::hal::SdCard {
public:
struct Config {
Config(
int spiFrequency,
gpio_num_t spiPinCs,
gpio_num_t spiPinCd,
gpio_num_t spiPinWp,
gpio_num_t spiPinInt,
MountBehaviour mountBehaviourAtBoot,
std::shared_ptr<Lockable> lockable = nullptr,
std::vector<gpio_num_t> csPinWorkAround = std::vector<gpio_num_t>(),
spi_host_device_t spiHost = SPI2_HOST
) : spiFrequency(spiFrequency),
spiPinCs(spiPinCs),
spiPinCd(spiPinCd),
spiPinWp(spiPinWp),
spiPinInt(spiPinInt),
mountBehaviourAtBoot(mountBehaviourAtBoot),
lockable(std::move(lockable)),
csPinWorkAround(std::move(csPinWorkAround)),
spiHost(spiHost)
{}
int spiFrequency;
gpio_num_t spiPinCs; // Clock
gpio_num_t spiPinCd; // Card detect
gpio_num_t spiPinWp; // Write-protect
gpio_num_t spiPinInt; // Interrupt
SdCard::MountBehaviour mountBehaviourAtBoot;
std::shared_ptr<Lockable> _Nullable lockable;
std::vector<gpio_num_t> csPinWorkAround;
spi_host_device_t spiHost;
bool formatOnMountFailed = false;
uint16_t maxOpenFiles = 4;
uint16_t allocUnitSize = 16 * 1024;
bool statusCheckEnabled = false;
};
private:
std::string mountPoint;
sdmmc_card_t* card = nullptr;
std::shared_ptr<Config> config;
bool applyGpioWorkAround();
bool mountInternal(const char* mount_point);
public:
explicit SpiSdCard(std::unique_ptr<Config> config) :
SdCard(config->mountBehaviourAtBoot),
config(std::move(config))
{}
bool mount(const char* mountPath) override;
bool unmount() override;
State getState() const override;
};
}
#endif
@@ -1,85 +0,0 @@
#include "Sdcard.h"
#include "Mutex.h"
#include "TactilityCore.h"
namespace tt::hal::sdcard {
#define TAG "sdcard"
static Mutex mutex(Mutex::TypeRecursive);
typedef struct {
const SdCard* sdcard;
void* context;
} MountData;
static MountData data = {
.sdcard = nullptr,
.context = nullptr
};
static bool lock(uint32_t timeout_ticks) {
return mutex.acquire(timeout_ticks) == TtStatusOk;
}
static void unlock() {
mutex.release();
}
bool mount(const SdCard* sdcard) {
TT_LOG_I(TAG, "Mounting");
if (data.sdcard != nullptr) {
TT_LOG_E(TAG, "Failed to mount: already mounted");
return false;
}
if (lock(100)) {
void* context = sdcard->mount(TT_SDCARD_MOUNT_POINT);
data = (MountData) {
.sdcard = sdcard,
.context = context
};
unlock();
return (data.context != nullptr);
} else {
TT_LOG_E(TAG, "Failed to lock");
return false;
}
}
State getState() {
if (data.context == nullptr) {
return StateUnmounted;
} else if (data.sdcard->is_mounted(data.context)) {
return StateMounted;
} else {
return StateError;
}
}
bool unmount(uint32_t timeout_ticks) {
TT_LOG_I(TAG, "Unmounting");
bool result = false;
if (lock(timeout_ticks)) {
if (data.sdcard != nullptr) {
data.sdcard->unmount(data.context);
data = (MountData) {
.sdcard = nullptr,
.context = nullptr
};
result = true;
} else {
TT_LOG_E(TAG, "Can't unmount: nothing mounted");
}
unlock();
} else {
TT_LOG_E(TAG, "Failed to lock in %lu ticks", timeout_ticks);
}
return result;
}
} // namespace
@@ -1,35 +0,0 @@
#pragma once
#include "TactilityCore.h"
namespace tt::hal::sdcard {
#define TT_SDCARD_MOUNT_POINT "/sdcard"
typedef void* (*Mount)(const char* mount_path);
typedef void (*Unmount)(void* context);
typedef bool (*IsMounted)(void* context);
typedef enum {
StateMounted,
StateUnmounted,
StateError,
} State;
typedef enum {
MountBehaviourAtBoot, /** Only mount at boot */
MountBehaviourAnytime /** Mount/dismount any time */
} MountBehaviour;
typedef struct {
Mount mount;
Unmount unmount;
IsMounted is_mounted;
MountBehaviour mount_behaviour;
} SdCard;
bool mount(const SdCard* sdcard);
State getState();
bool unmount(uint32_t timeout_ticks);
} // namespace