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:
committed by
GitHub
parent
9033daa6dd
commit
50bd6e8bf6
@@ -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.
|
||||
|
||||
@@ -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)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user