diff --git a/include/sta/drivers/w25qxx.hpp b/include/sta/drivers/w25qxx.hpp index 9d93c20..b6d04ad 100644 --- a/include/sta/drivers/w25qxx.hpp +++ b/include/sta/drivers/w25qxx.hpp @@ -2,6 +2,7 @@ #define STA_SENSORS_W25Q128_HPP #include +#include #include #include @@ -60,12 +61,14 @@ namespace sta using DelayUsFunc = void (*)(uint32_t); /** - * @brief Construct a new W25Qxx object + * @brief Driver class for the W25QXX flash storage series. * - * @param device - * @param addrMode + * @param device A SPI device handle from sta-core. + * @param delay A microsecond delay function. + * @param addrMode Choose between 24 Bit and 32 Bit addressing. + * @param mutex A mutex for thread safety if the flash chip is used by multiple threads. Defaults to a always free mutex. */ - W25Qxx(SPIDevice * device, DelayUsFunc delay, AddressMode addrMode = AddressMode::_24BIT); + W25Qxx(SPIDevice * device, DelayUsFunc delay, AddressMode addrMode = AddressMode::_24BIT, Mutex * mutex = Mutex::ALWAYS_FREE); /** * @brief Initialize the flash chip. @@ -74,18 +77,12 @@ namespace sta */ uint8_t init(); - uint32_t getChunkBytes(ChunkSize size); - /** - * @brief Find the first memory section not satisfying a given criterion using a binary search. + * @brief Checks if the flash is busy writing or erasing. * - * @note This function assume that there is a page n such that every page before n satisfies the criterion, while every page after that doesn't - * - * @param criterion A function evaluating the criterion on a page. - * @param size The size of the memory section. Has to be one of the predefined sizes. - * @return uint32_t The last address such that the criterion is satisfied. + * @return bool Returns true if the flash is busy, false otherwise. */ - uint32_t findLast(std::function criterion, ChunkSize size); + bool isBusy(); /** * @brief Set the Address Mode object @@ -146,41 +143,7 @@ namespace sta // reset device - // Extended address register read / write? - - // Enter 4-Byte address mode - - // Exit 4-Byte address mode - public: - /* - * Status registers. - */ - - /** - * @brief Read one of the flash's status registers. - * - * @param regID The ID of the status register. Can only be 1, 2 or 3. - * @param status_byte A pointer to the variable to write the state into. - * @return uint8_t Returns 1 if successful, 0 otherwise. - */ - uint8_t readStatusRegister(uint8_t regID, uint8_t * status_byte); - - /** - * @brief Write into one of the chip's status registers. - * - * @param regID The ID of the status register. Can only be 1, 2 or 3. - * @param status_byte The byte to write into the status register. - * @param nonvolatile If set to true, this setting will be restored after power off. - * @return uint8_t Returns 1 if successful, 0 otherwise. - */ - uint8_t writeStatusRegister(uint8_t regID, uint8_t * status_byte, bool nonvolatile = false); - - /** - * @brief Checks if the flash is busy writing or erasing. - * - * @return bool Returns true if the flash is busy, false otherwise. - */ - bool isBusy(); + // Extended address register read / write? public: /* * Read / Write operations @@ -218,10 +181,10 @@ namespace sta * @remarks Afterwards, the device won't accept any instructions for a duration T_SE. This can be checked * by reading the busy bit. * - * @param address The address of the sector to erase. + * @param address The number of the sector to erase. Here, 0 is the first sector, 1 the second and so on. * @return bool Returns 1 if the operation was successful, 0 otherwise. */ - uint8_t sectorErase(uint32_t address); + uint8_t sectorErase(uint32_t address, bool blocking = false); /** * @brief Sets all memory within a specified block (32/64 KByte) to 1s. @@ -315,13 +278,32 @@ namespace sta uint8_t writeVolatileEnable(); + /** + * @brief Read one of the flash's status registers. + * + * @param regID The ID of the status register. Can only be 1, 2 or 3. + * @param status_byte A pointer to the variable to write the state into. + * @return uint8_t Returns 1 if successful, 0 otherwise. + */ + uint8_t readStatusRegister(uint8_t regID, uint8_t * status_byte); + + /** + * @brief Write into one of the chip's status registers. + * + * @param regID The ID of the status register. Can only be 1, 2 or 3. + * @param status_byte The byte to write into the status register. + * @param nonvolatile If set to true, this setting will be restored after power off. + * @return uint8_t Returns 1 if successful, 0 otherwise. + */ + uint8_t writeStatusRegister(uint8_t regID, uint8_t * status_byte, bool nonvolatile = false); private: SPIDevice * device_; DelayUsFunc delay_; + Mutex * mutex_; ChipState state_; AddressMode addrMode_; }; } // namespace sta -#endif // STA_SENSORS_W25Q128_HPP \ No newline at end of file +#endif // STA_SENSORS_W25Q128_HPP diff --git a/include/sta/drivers/w25qxx_defs.hpp b/include/sta/drivers/w25qxx_defs.hpp index 64fc3b5..897b625 100644 --- a/include/sta/drivers/w25qxx_defs.hpp +++ b/include/sta/drivers/w25qxx_defs.hpp @@ -39,12 +39,14 @@ #define W25QXX_PAGE_PROGAM 0x02 #define W25QXX_QUAD_PAGE_PROGAM 0x32 -#define W25QXX_SECTOR_ERASE 0x21 +#define W25QXX_SECTOR_ERASE 0x20 #define W25QXX_BLOCK_ERASE_32_KB 0x52 #define W25QXX_BLOCK_ERASE_64_KB 0xD8 #define W25QXX_READ 0x03 +#define W25QXX_READ_32_BIT 0x13 #define W25QXX_FAST_READ 0x0B +#define W25QXX_FAST_READ_32_BIT 0x0C #define W25QXX_FAST_READ_DUAL_OUT 0x3B #define W25QXX_FAST_READ_QUAD_OUT 0x6B #define W25QXX_SFDP_REG 0x5A @@ -61,6 +63,7 @@ #define W25QXX_PAGE_SIZE 0x100 #define W25QXX_SECTOR_SIZE 0x1000 +#define W25QXX_PAGE_PER_SECTOR 0x10 #define W25QXX_BLOCK_32_KB_SIZE 0x8000 #define W25QXX_BLOCK_64_KB_SIZE 0xF000 diff --git a/include/sta/utils/logger.hpp b/include/sta/utils/logger.hpp new file mode 100644 index 0000000..6a7e747 --- /dev/null +++ b/include/sta/utils/logger.hpp @@ -0,0 +1,103 @@ +#ifndef STA_UTILS_LOGGER_HPP +#define STA_UTILS_LOGGER_HPP + +#include +#include + +#include + +namespace sta +{ + template + class Logger + { + public: + /** + * @brief Constructs a logger object which manages reading and writing data to a segment of a flash chip. + * + * @param flash The flash chip to use for logging. + * @param startSector The index of the start sector (1 LSB = 4096 bytes). + * @param endSector The index of the end sector (1 LSB = 4096 bytes). + */ + Logger(W25Qxx * flash, uint32_t startSector, uint32_t endSector); + + /** + * @brief Write a new data point to the flash chip as long as the segment's limit wasn't reached. + * + * @param data A pointer to the data to write to the flash chip. + * @return true if successful, false if the segment end was reached. + */ + bool write(T* data); + + /** + * @brief Write a new data point to the flash chip as long as the segment's limit wasn't reached. + * + * @param data The data to write to the flash chip. + * @return true if successful, false if the segment end was reached. + */ + bool write(T data); + + /** + * @brief Clear the flash memory used by the logger. + * + */ + void clear(); + + /** + * @brief Get the number of data points currently written to the memory segment. + * + * @return size_t The number of data points. + */ + size_t count(); + + /** + * @brief Get the number of data points that can be written to the logger before an overflow occurs. + * + * @return size_t The number of data points. + */ + size_t remaining(); + + /** + * @brief Get the total number of data points that fit into the logger. + * + * @return size_t The number of data points. + */ + size_t capacity(); + + /** + * @brief Get the ith element stored in the flash storage. + * + * @param i The index of the element to read. + * @return T The ith element stored in the flash storage. + */ + T get(std::size_t i); + private: + /** + * @brief Find the first sector in the segment that was not written yet. + * + */ + void findLast(); + + /** + * @brief A method that checks if a page is empty + * + * @param buffer A buffer of size 256 containing data from a page. + * @return true if the page is empty, false otherwise. + */ + bool searchCriterion(uint8_t * buffer); + + private: + W25Qxx * flash_; + uint32_t start_; + uint32_t end_; + uint32_t address_; + + uint8_t buffer_[W25QXX_PAGE_SIZE]; + bool flushed_; + uint32_t ptr_; + }; +} // namespace sta + +#include + +#endif // STA_UTILS_LOGGER_HPP diff --git a/include/sta/utils/logger.tpp b/include/sta/utils/logger.tpp new file mode 100644 index 0000000..6d8bbb5 --- /dev/null +++ b/include/sta/utils/logger.tpp @@ -0,0 +1,205 @@ +#ifndef STA_UTILS_LOGGER_TPP +#define STA_UTILS_LOGGER_TPP + +#include +#include + +namespace sta +{ + template + Logger::Logger(W25Qxx * flash, uint32_t startSec, uint32_t endSec) + : flash_{flash}, + start_{startSec}, + end_{endSec}, + address_{start_ * W25QXX_SECTOR_SIZE}, + buffer_{0x00, }, + flushed_{false}, + ptr_ {0} + { + STA_ASSERT(flash != nullptr); + STA_ASSERT(endSec > startSec); + + // Jump to the last written page. + findLast(); + } + + template + bool Logger::searchCriterion(uint8_t * buffer) + { + for (size_t i = 0; i < W25QXX_PAGE_SIZE; i++) + { + if (buffer[i] != 0xFF) + { + return false; + } + } + + return true; + } + + template + void Logger::findLast() + { + uint32_t left = start_; + uint32_t right = end_+1; + uint32_t middle; + + uint8_t * buffer = new uint8_t[256]; + + while (left <= right) + { + middle = (left + right) / 2; + flash_->readData(middle * W25QXX_SECTOR_SIZE, buffer, W25QXX_PAGE_SIZE); + + if (middle == 0 && searchCriterion(buffer)) + { + break; + } + + if (searchCriterion(buffer)) + { + if (middle == 0) + { + break; + } + + flash_->readData((middle-1) * W25QXX_SECTOR_SIZE, buffer, W25QXX_PAGE_SIZE); + if (!searchCriterion(buffer)) + { + break; + } + + right = middle; + } + else + { + left = middle + 1; + } + } + + middle = (left + right) / 2; + delete[] buffer; + + address_ = middle * W25QXX_SECTOR_SIZE; + ptr_ = 0; + } + + template + bool Logger::write(T* data) + { + // If writing the data would exceed the segment length, return false and don't do anything. + if ((address_ + ptr_ + sizeof(T)) / W25QXX_SECTOR_SIZE >= end_) + { + // If the current page hasn't been uploaded yet, do it now. + if (!flushed_) + { + flash_->pageProgram(address_, buffer_, W25QXX_PAGE_SIZE); + flushed_ = true; + } + return false; + } + + // Convert the data to a byte array. + uint8_t * bytes = reinterpret_cast(data); + uint32_t length = sizeof(T); + + // If the written data exceeds the remaining bytes in the page. + while (ptr_ + length >= W25QXX_PAGE_SIZE) + { + // Bytes remaining until the page is full. + uint32_t remaining = W25QXX_PAGE_SIZE - ptr_; + + // If the page to written is in a new sector, erase the new sector before writing to it. + if (address_ % W25QXX_SECTOR_SIZE == 0) + { + flash_->sectorErase(address_); + } + + std::memcpy(buffer_ + ptr_, bytes, remaining); + flash_->pageProgram(address_, buffer_, W25QXX_PAGE_SIZE); + + bytes += remaining; + length -= remaining; + ptr_ = 0; + address_ += W25QXX_PAGE_SIZE; + } + + std::memcpy(buffer_ + ptr_, bytes, length); + ptr_ += length; + + return true; + } + + template + bool Logger::write(T data) + { + return write(&data); + } + + template + void Logger::clear() + { + uint32_t left = start_; + uint32_t right = end_+1; + uint32_t middle; + + // Erase all sectors the binary search would check when trying to find the last written page. + while (left <= right) + { + middle = (left + right) / 2; + flash_->sectorErase(middle * W25QXX_SECTOR_SIZE, true); + right = middle; + + if (middle == 0) + break; + + flash_->sectorErase((middle-1) * W25QXX_SECTOR_SIZE, true); + } + + middle = (left + right) / 2; + flash_->sectorErase(middle * W25QXX_SECTOR_SIZE, true); + + address_ = start_ * W25QXX_SECTOR_SIZE; + ptr_ = 0; + flushed_ = false; + } + + template + size_t Logger::count() + { + return ((address_ + ptr_) - start_ * W25QXX_SECTOR_SIZE) / sizeof(T); + } + + template + size_t Logger::remaining() + { + return (end_ * W25QXX_SECTOR_SIZE - (address_ + ptr_)) / sizeof(T); + } + + template + size_t Logger::capacity() + { + return (end_ - start_) * W25QXX_SECTOR_SIZE / sizeof(T); + } + + template + T Logger::get(std::size_t i) + { + uint32_t address = start_ * W25QXX_SECTOR_SIZE + i * sizeof(T); + + // If the requested element is in the cache, read it from there. + if (address / W25QXX_PAGE_SIZE == address_ / W25QXX_PAGE_SIZE) + { + uint8_t * ptr = buffer_ + address % W25QXX_PAGE_SIZE; + return *reinterpret_cast(ptr); + } + else + { + uint8_t buffer[sizeof(T)]; + flash_->readData(address, buffer, sizeof(T)); + return *reinterpret_cast(buffer); + } + } +} // namespace sta + +#endif // STA_UTILS_LOGGER_TPP diff --git a/src/w25qxx.cpp b/src/w25qxx.cpp index 3351bbe..4a62e68 100644 --- a/src/w25qxx.cpp +++ b/src/w25qxx.cpp @@ -9,17 +9,21 @@ namespace sta { - W25Qxx::W25Qxx(SPIDevice * device, DelayUsFunc delay, AddressMode addrMode /* = AddressMode::_24BIT */) + W25Qxx::W25Qxx(SPIDevice * device, DelayUsFunc delay, AddressMode addrMode /* = AddressMode::_24BIT */, Mutex * mutex /* = Mutex::ALWAYS_FREE */) : device_{device}, delay_{delay}, + mutex_{mutex}, state_{ChipState::POWERED_DOWN}, addrMode_{addrMode} { STA_ASSERT(device != nullptr); + STA_ASSERT(mutex != nullptr); } uint8_t W25Qxx::init() { + lock_guardlock(*mutex_); + powerDown(); delay_(5); @@ -46,69 +50,9 @@ namespace sta return 1; } - uint32_t W25Qxx::getChunkBytes(ChunkSize size) - { - switch (size) - { - case ChunkSize::PAGE: - return W25QXX_PAGE_SIZE; - - case ChunkSize::SECTOR: - return W25QXX_SECTOR_SIZE; - - case ChunkSize::BLOCK_32KB: - return W25QXX_BLOCK_32_KB_SIZE; - - case ChunkSize::BLOCK_64KB: - return W25QXX_BLOCK_64_KB_SIZE; - - default: - STA_UNREACHABLE(); - break; - } - } - - uint32_t W25Qxx::findLast(std::function criterion, ChunkSize size) - { - uint32_t bytes = getChunkBytes(size); - - uint32_t left = 0; - uint32_t right = (addrMode_ == AddressMode::_32BIT ? W25QXX_32B_MEM_SIZE : W25QXX_24B_MEM_SIZE) / bytes; - uint32_t middle; - - uint8_t * buffer = new uint8_t[bytes]; - - while (left < right) - { - middle = (left + right) / 2; - STA_DEBUG_PRINTF("left=%d, middle=%d, right=%d", left, middle, right); - readData(middle * bytes, buffer, bytes); - - if (criterion(buffer)) - { - left = middle + 1; - } - else - { - right = middle - 1; - } - } - - middle = (left + right) / 2; - - readData(middle * bytes, buffer, bytes); - if (criterion(buffer)) - { - middle += 1; - } - - delete[] buffer; - - return middle * bytes; - } - uint8_t W25Qxx::setAddressMode(AddressMode addrMode) { + lock_guardlock(*mutex_); busWrite(W25QXX_4_BYTE_ADDR_ENABLE); while (isBusy()) {} @@ -118,6 +62,8 @@ namespace sta AddressMode W25Qxx::getAddressMode() { + lock_guardlock(*mutex_); + uint8_t status; readStatusRegister(3, &status); @@ -126,6 +72,8 @@ namespace sta uint8_t W25Qxx::getChipID() { + lock_guardlock(*mutex_); + uint8_t buffer[4]; busRead(W25QXX_RELEASE_POWER_DOWN, buffer, 3); @@ -134,6 +82,8 @@ namespace sta uint8_t W25Qxx::getManufacturerID() { + lock_guardlock(*mutex_); + uint8_t dummy[3] = {0, 0, 0}; uint8_t id[2] = {0, 0}; @@ -144,6 +94,8 @@ namespace sta uint64_t W25Qxx::getUniqueID() { + lock_guardlock(*mutex_); + uint8_t dummy[4]; uint8_t id[8]; @@ -151,7 +103,7 @@ namespace sta uint64_t id_complete = 0; - for (size_t i; i < 8; i++) + for (size_t i = 0; i < 8; i++) { id_complete |= id[i] << (7-i) * 8; } @@ -161,6 +113,8 @@ namespace sta bool W25Qxx::isBusy() { + lock_guardlock(*mutex_); + uint8_t status = 0; readStatusRegister(1, &status); @@ -169,27 +123,38 @@ namespace sta uint8_t W25Qxx::readData(uint32_t address, uint8_t * buffer, size_t length, bool fast /* = true */) { - uint8_t instruction = fast ? W25QXX_FAST_READ : W25QXX_READ; + lock_guardlock(*mutex_); - // In fast mode we have to send a 8 dummy clock cycles first. - if (fast) - { - // TODO - } + uint8_t instruction = fast ? W25QXX_FAST_READ : W25QXX_READ; while (isBusy()) {} // Depending on address mode, send 3 bytes or 4 bytes. if (addrMode_ == AddressMode::_32BIT) { - uint8_t addrBuffer[4] = { - (uint8_t) (address >> 24), - (uint8_t) (address >> 16), - (uint8_t) (address >> 8), - (uint8_t) (address) - }; + if (fast) + { + uint8_t addrBuffer[5] = { + (uint8_t) (address >> 24), + (uint8_t) (address >> 16), + (uint8_t) (address >> 8), + (uint8_t) (address), + 0x00 // Dummy byte for fast mode + }; - return busRead(instruction, buffer, length, addrBuffer, 4); + return busRead(instruction, buffer, length, addrBuffer, 5); + } + else + { + uint8_t addrBuffer[5] = { + (uint8_t) (address >> 24), + (uint8_t) (address >> 16), + (uint8_t) (address >> 8), + (uint8_t) (address) + }; + + return busRead(instruction, buffer, length, addrBuffer, 4); + } } else { @@ -219,6 +184,8 @@ namespace sta uint8_t W25Qxx::pageProgram(uint32_t address, uint8_t * buffer, size_t length) { + lock_guardlock(*mutex_); + STA_ASSERT(length <= W25QXX_PAGE_SIZE); while (isBusy()) {} @@ -254,6 +221,8 @@ namespace sta uint8_t W25Qxx::sectorProgram(uint32_t address, uint8_t * buffer, size_t length) { + lock_guardlock(*mutex_); + STA_ASSERT(length <= W25QXX_SECTOR_SIZE); uint32_t nPages = length / W25QXX_PAGE_SIZE; @@ -399,8 +368,15 @@ namespace sta return (0x02 & status) == 0x02; } - uint8_t W25Qxx::sectorErase(uint32_t address) + uint8_t W25Qxx::sectorErase(uint32_t address, bool blocking /* = false */) { + if (address % W25QXX_SECTOR_SIZE != 0) + { + return 0; + } + + while (isBusy()) {} + if (!writeEnable()) { return 0; @@ -410,31 +386,36 @@ namespace sta if (addrMode_ == AddressMode::_32BIT) { - uint8_t addrBuffer[4] = { - (uint8_t) (address >> 24), - (uint8_t) (address >> 16), - (uint8_t) (address >> 8), - (uint8_t) (address) - }; + uint8_t addrBuffer[4] = { + (uint8_t) (address >> 24), + (uint8_t) (address >> 16), + (uint8_t) (address >> 8), + (uint8_t) (address) + }; - return busWrite(W25QXX_SECTOR_ERASE, addrBuffer, 4); + return busWrite(W25QXX_SECTOR_ERASE, addrBuffer, 4); } else { - uint8_t addrBuffer[3] = { - (uint8_t) (address >> 16), - (uint8_t) (address >> 8), - (uint8_t) (address) - }; + uint8_t addrBuffer[3] = { + (uint8_t) (address >> 16), + (uint8_t) (address >> 8), + (uint8_t) (address) + }; - return busWrite(W25QXX_SECTOR_ERASE, addrBuffer, 3); + return busWrite(W25QXX_SECTOR_ERASE, addrBuffer, 3); } + if (blocking) + delay_(200*1000); + return 1; } uint8_t W25Qxx::blockErase(uint32_t address, BlockSize blockSize) { + while (isBusy()) {} + if (!writeEnable()) { return 0;