mirror of https://github.com/meshcore-dev/MeshCore
committed by
GitHub
185 changed files with 14024 additions and 755 deletions
@ -0,0 +1 @@ |
|||
use nix |
|||
@ -1,6 +1,10 @@ |
|||
.direnv |
|||
.pio |
|||
.vscode/.browse.c_cpp.db* |
|||
.vscode/c_cpp_properties.json |
|||
.vscode/launch.json |
|||
.vscode/ipch |
|||
out/ |
|||
.direnv/ |
|||
.DS_Store |
|||
.vscode/settings.json |
|||
|
|||
@ -0,0 +1 @@ |
|||
This is LittleFS from Adafruit, stripped from things that makes it not compile with stm32 (refs to TinyUSB and free_rtos, mostly) |
|||
@ -0,0 +1,10 @@ |
|||
name=Adafruit Little File System Libraries |
|||
version=0.11.0 |
|||
author=Adafruit |
|||
maintainer=Adafruit <[email protected]> |
|||
sentence=Arduino library for ARM Little File System |
|||
paragraph=Arduino library for ARM Little File System |
|||
category=Data Storage |
|||
url=https://github.com/adafruit/Adafruit_nRF52_Arduino |
|||
architectures=* |
|||
includes=Adafruit_LittleFS.h |
|||
@ -0,0 +1,273 @@ |
|||
/*
|
|||
* The MIT License (MIT) |
|||
* |
|||
* Copyright (c) 2019 Ha Thach for Adafruit Industries |
|||
* |
|||
* Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
* of this software and associated documentation files (the "Software"), to deal |
|||
* in the Software without restriction, including without limitation the rights |
|||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
* copies of the Software, and to permit persons to whom the Software is |
|||
* furnished to do so, subject to the following conditions: |
|||
* |
|||
* The above copyright notice and this permission notice shall be included in |
|||
* all copies or substantial portions of the Software. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|||
* THE SOFTWARE. |
|||
*/ |
|||
|
|||
#include <Arduino.h> |
|||
#include <string.h> |
|||
#include "Adafruit_LittleFS.h" |
|||
|
|||
//#include <Adafruit_TinyUSB.h> // for Serial
|
|||
|
|||
using namespace Adafruit_LittleFS_Namespace; |
|||
|
|||
#define memclr(buffer, size) memset(buffer, 0, size) |
|||
#define varclr(_var) memclr(_var, sizeof(*(_var))) |
|||
|
|||
//--------------------------------------------------------------------+
|
|||
// Implementation
|
|||
//--------------------------------------------------------------------+
|
|||
|
|||
Adafruit_LittleFS::Adafruit_LittleFS (void) |
|||
: Adafruit_LittleFS(NULL) |
|||
{ |
|||
|
|||
} |
|||
|
|||
Adafruit_LittleFS::Adafruit_LittleFS (struct lfs_config* cfg) |
|||
{ |
|||
varclr(&_lfs); |
|||
_lfs_cfg = cfg; |
|||
_mounted = false; |
|||
// _mutex = xSemaphoreCreateMutexStatic(&this->_MutexStorageSpace);
|
|||
} |
|||
|
|||
Adafruit_LittleFS::~Adafruit_LittleFS () |
|||
{ |
|||
|
|||
} |
|||
|
|||
// Initialize and mount the file system
|
|||
// Return true if mounted successfully else probably corrupted.
|
|||
// User should format the disk and try again
|
|||
bool Adafruit_LittleFS::begin (struct lfs_config * cfg) |
|||
{ |
|||
_lockFS(); |
|||
|
|||
bool ret; |
|||
// not a loop, just an quick way to short-circuit on error
|
|||
do { |
|||
if (_mounted) { ret = true; break; } |
|||
if (cfg) { _lfs_cfg = cfg; } |
|||
if (nullptr == _lfs_cfg) { ret = false; break; } |
|||
// actually attempt to mount, and log error if one occurs
|
|||
int err = lfs_mount(&_lfs, _lfs_cfg); |
|||
PRINT_LFS_ERR(err); |
|||
_mounted = (err == LFS_ERR_OK); |
|||
ret = _mounted; |
|||
} while(0); |
|||
|
|||
_unlockFS(); |
|||
return ret; |
|||
} |
|||
|
|||
// Tear down and unmount file system
|
|||
void Adafruit_LittleFS::end(void) |
|||
{ |
|||
_lockFS(); |
|||
|
|||
if (_mounted) |
|||
{ |
|||
_mounted = false; |
|||
int err = lfs_unmount(&_lfs); |
|||
PRINT_LFS_ERR(err); |
|||
(void)err; |
|||
} |
|||
|
|||
_unlockFS(); |
|||
} |
|||
|
|||
bool Adafruit_LittleFS::format (void) |
|||
{ |
|||
_lockFS(); |
|||
|
|||
int err = LFS_ERR_OK; |
|||
bool attemptMount = _mounted; |
|||
// not a loop, just an quick way to short-circuit on error
|
|||
do |
|||
{ |
|||
// if already mounted: umount first -> format -> remount
|
|||
if (_mounted) |
|||
{ |
|||
_mounted = false; |
|||
err = lfs_unmount(&_lfs); |
|||
if ( LFS_ERR_OK != err) { PRINT_LFS_ERR(err); break; } |
|||
} |
|||
err = lfs_format(&_lfs, _lfs_cfg); |
|||
if ( LFS_ERR_OK != err ) { PRINT_LFS_ERR(err); break; } |
|||
|
|||
if (attemptMount) |
|||
{ |
|||
err = lfs_mount(&_lfs, _lfs_cfg); |
|||
if ( LFS_ERR_OK != err ) { PRINT_LFS_ERR(err); break; } |
|||
_mounted = true; |
|||
} |
|||
// success!
|
|||
} while(0); |
|||
|
|||
_unlockFS(); |
|||
return LFS_ERR_OK == err; |
|||
} |
|||
|
|||
// Open a file or folder
|
|||
Adafruit_LittleFS_Namespace::File Adafruit_LittleFS::open (char const *filepath, uint8_t mode) |
|||
{ |
|||
// No lock is required here ... the File() object will synchronize with the mutex provided
|
|||
return Adafruit_LittleFS_Namespace::File(filepath, mode, *this); |
|||
} |
|||
|
|||
// Check if file or folder exists
|
|||
bool Adafruit_LittleFS::exists (char const *filepath) |
|||
{ |
|||
struct lfs_info info; |
|||
_lockFS(); |
|||
|
|||
bool ret = (0 == lfs_stat(&_lfs, filepath, &info)); |
|||
|
|||
_unlockFS(); |
|||
return ret; |
|||
} |
|||
|
|||
|
|||
// Create a directory, create intermediate parent if needed
|
|||
bool Adafruit_LittleFS::mkdir (char const *filepath) |
|||
{ |
|||
bool ret = true; |
|||
const char* slash = filepath; |
|||
if ( slash[0] == '/' ) slash++; // skip root '/'
|
|||
|
|||
_lockFS(); |
|||
|
|||
// make intermediate parent directory(ies)
|
|||
while ( NULL != (slash = strchr(slash, '/')) ) |
|||
{ |
|||
char parent[slash - filepath + 1] = { 0 }; |
|||
memcpy(parent, filepath, slash - filepath); |
|||
|
|||
int rc = lfs_mkdir(&_lfs, parent); |
|||
if ( rc != LFS_ERR_OK && rc != LFS_ERR_EXIST ) |
|||
{ |
|||
PRINT_LFS_ERR(rc); |
|||
ret = false; |
|||
break; |
|||
} |
|||
slash++; |
|||
} |
|||
// make the final requested directory
|
|||
if (ret) |
|||
{ |
|||
int rc = lfs_mkdir(&_lfs, filepath); |
|||
if ( rc != LFS_ERR_OK && rc != LFS_ERR_EXIST ) |
|||
{ |
|||
PRINT_LFS_ERR(rc); |
|||
ret = false; |
|||
} |
|||
} |
|||
|
|||
_unlockFS(); |
|||
return ret; |
|||
} |
|||
|
|||
// Remove a file
|
|||
bool Adafruit_LittleFS::remove (char const *filepath) |
|||
{ |
|||
_lockFS(); |
|||
|
|||
int err = lfs_remove(&_lfs, filepath); |
|||
PRINT_LFS_ERR(err); |
|||
|
|||
_unlockFS(); |
|||
return LFS_ERR_OK == err; |
|||
} |
|||
|
|||
// Rename a file
|
|||
bool Adafruit_LittleFS::rename (char const *oldfilepath, char const *newfilepath) |
|||
{ |
|||
_lockFS(); |
|||
|
|||
int err = lfs_rename(&_lfs, oldfilepath, newfilepath); |
|||
PRINT_LFS_ERR(err); |
|||
|
|||
_unlockFS(); |
|||
return LFS_ERR_OK == err; |
|||
} |
|||
|
|||
// Remove a folder
|
|||
bool Adafruit_LittleFS::rmdir (char const *filepath) |
|||
{ |
|||
_lockFS(); |
|||
|
|||
int err = lfs_remove(&_lfs, filepath); |
|||
PRINT_LFS_ERR(err); |
|||
|
|||
_unlockFS(); |
|||
return LFS_ERR_OK == err; |
|||
} |
|||
|
|||
// Remove a folder recursively
|
|||
bool Adafruit_LittleFS::rmdir_r (char const *filepath) |
|||
{ |
|||
/* adafruit: lfs is modified to remove non-empty folder,
|
|||
According to below issue, comment these 2 line won't corrupt filesystem |
|||
at least when using LFS v1. If moving to LFS v2, see tracked issue |
|||
to see if issues (such as the orphans in threaded linked list) are resolved. |
|||
https://github.com/ARMmbed/littlefs/issues/43
|
|||
*/ |
|||
_lockFS(); |
|||
|
|||
int err = lfs_remove(&_lfs, filepath); |
|||
PRINT_LFS_ERR(err); |
|||
|
|||
_unlockFS(); |
|||
return LFS_ERR_OK == err; |
|||
} |
|||
|
|||
//------------- Debug -------------//
|
|||
#if CFG_DEBUG |
|||
|
|||
const char* dbg_strerr_lfs (int32_t err) |
|||
{ |
|||
switch ( err ) |
|||
{ |
|||
case LFS_ERR_OK : return "LFS_ERR_OK"; |
|||
case LFS_ERR_IO : return "LFS_ERR_IO"; |
|||
case LFS_ERR_CORRUPT : return "LFS_ERR_CORRUPT"; |
|||
case LFS_ERR_NOENT : return "LFS_ERR_NOENT"; |
|||
case LFS_ERR_EXIST : return "LFS_ERR_EXIST"; |
|||
case LFS_ERR_NOTDIR : return "LFS_ERR_NOTDIR"; |
|||
case LFS_ERR_ISDIR : return "LFS_ERR_ISDIR"; |
|||
case LFS_ERR_NOTEMPTY : return "LFS_ERR_NOTEMPTY"; |
|||
case LFS_ERR_BADF : return "LFS_ERR_BADF"; |
|||
case LFS_ERR_INVAL : return "LFS_ERR_INVAL"; |
|||
case LFS_ERR_NOSPC : return "LFS_ERR_NOSPC"; |
|||
case LFS_ERR_NOMEM : return "LFS_ERR_NOMEM"; |
|||
|
|||
default: |
|||
static char errcode[10]; |
|||
sprintf(errcode, "%ld", err); |
|||
return errcode; |
|||
} |
|||
|
|||
return NULL; |
|||
} |
|||
|
|||
#endif |
|||
@ -0,0 +1,102 @@ |
|||
/*
|
|||
* The MIT License (MIT) |
|||
* |
|||
* Copyright (c) 2019 Ha Thach for Adafruit Industries |
|||
* |
|||
* Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
* of this software and associated documentation files (the "Software"), to deal |
|||
* in the Software without restriction, including without limitation the rights |
|||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
* copies of the Software, and to permit persons to whom the Software is |
|||
* furnished to do so, subject to the following conditions: |
|||
* |
|||
* The above copyright notice and this permission notice shall be included in |
|||
* all copies or substantial portions of the Software. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|||
* THE SOFTWARE. |
|||
*/ |
|||
|
|||
#ifndef ADAFRUIT_LITTLEFS_H_ |
|||
#define ADAFRUIT_LITTLEFS_H_ |
|||
|
|||
#include <Stream.h> |
|||
|
|||
// Internal Flash uses ARM Little FileSystem
|
|||
// https://github.com/ARMmbed/littlefs
|
|||
#include "littlefs/lfs.h" |
|||
#include "Adafruit_LittleFS_File.h" |
|||
//#include "rtos.h" // tied to FreeRTOS for serialization
|
|||
|
|||
class Adafruit_LittleFS |
|||
{ |
|||
public: |
|||
Adafruit_LittleFS (void); |
|||
Adafruit_LittleFS (struct lfs_config* cfg); |
|||
virtual ~Adafruit_LittleFS (); |
|||
|
|||
bool begin(struct lfs_config * cfg = NULL); |
|||
void end(void); |
|||
|
|||
// Open the specified file/directory with the supplied mode (e.g. read or
|
|||
// write, etc). Returns a File object for interacting with the file.
|
|||
// Note that currently only one file can be open at a time.
|
|||
Adafruit_LittleFS_Namespace::File open (char const *filename, uint8_t mode = Adafruit_LittleFS_Namespace::FILE_O_READ); |
|||
|
|||
// Methods to determine if the requested file path exists.
|
|||
bool exists (char const *filepath); |
|||
|
|||
// Create the requested directory hierarchy--if intermediate directories
|
|||
// do not exist they will be created.
|
|||
bool mkdir (char const *filepath); |
|||
|
|||
// Delete the file.
|
|||
bool remove (char const *filepath); |
|||
|
|||
// Rename the file.
|
|||
bool rename (char const *oldfilepath, char const *newfilepath); |
|||
|
|||
// Delete a folder (must be empty)
|
|||
bool rmdir (char const *filepath); |
|||
|
|||
// Delete a folder (recursively)
|
|||
bool rmdir_r (char const *filepath); |
|||
|
|||
// format file system
|
|||
bool format (void); |
|||
|
|||
/*------------------------------------------------------------------*/ |
|||
/* INTERNAL USAGE ONLY
|
|||
* Although declare as public, it is meant to be invoked by internal |
|||
* code. User should not call these directly |
|||
*------------------------------------------------------------------*/ |
|||
lfs_t* _getFS (void) { return &_lfs; } |
|||
void _lockFS (void) { }//xSemaphoreTake(_mutex, portMAX_DELAY); }
|
|||
void _unlockFS(void) { }//xSemaphoreGive(_mutex); }
|
|||
|
|||
protected: |
|||
bool _mounted; |
|||
struct lfs_config* _lfs_cfg; |
|||
lfs_t _lfs; |
|||
// SemaphoreHandle_t _mutex;
|
|||
|
|||
private: |
|||
// StaticSemaphore_t _MutexStorageSpace;
|
|||
}; |
|||
|
|||
#if !CFG_DEBUG |
|||
#define VERIFY_LFS(...) _GET_3RD_ARG(__VA_ARGS__, VERIFY_ERR_2ARGS, VERIFY_ERR_1ARGS)(__VA_ARGS__, NULL) |
|||
#define PRINT_LFS_ERR(_err) |
|||
#else |
|||
#define VERIFY_LFS(...) _GET_3RD_ARG(__VA_ARGS__, VERIFY_ERR_2ARGS, VERIFY_ERR_1ARGS)(__VA_ARGS__, dbg_strerr_lfs) |
|||
#define PRINT_LFS_ERR(_err) do { if (_err) { VERIFY_MESS((long int)_err, dbg_strerr_lfs); } } while(0) // LFS_ERR are of type int, VERIFY_MESS expects long_int
|
|||
|
|||
const char* dbg_strerr_lfs (int32_t err); |
|||
#endif |
|||
|
|||
#endif /* ADAFRUIT_LITTLEFS_H_ */ |
|||
@ -0,0 +1,420 @@ |
|||
/*
|
|||
* The MIT License (MIT) |
|||
* |
|||
* Copyright (c) 2019 Ha Thach for Adafruit Industries |
|||
* |
|||
* Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
* of this software and associated documentation files (the "Software"), to deal |
|||
* in the Software without restriction, including without limitation the rights |
|||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
* copies of the Software, and to permit persons to whom the Software is |
|||
* furnished to do so, subject to the following conditions: |
|||
* |
|||
* The above copyright notice and this permission notice shall be included in |
|||
* all copies or substantial portions of the Software. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|||
* THE SOFTWARE. |
|||
*/ |
|||
|
|||
#include <Arduino.h> |
|||
#include "Adafruit_LittleFS.h" |
|||
#include "littlefs/lfs.h" |
|||
|
|||
//--------------------------------------------------------------------+
|
|||
// MACRO TYPEDEF CONSTANT ENUM DECLARATION
|
|||
//--------------------------------------------------------------------+
|
|||
|
|||
using namespace Adafruit_LittleFS_Namespace; |
|||
|
|||
File::File (Adafruit_LittleFS &fs) |
|||
{ |
|||
_fs = &fs; |
|||
_is_dir = false; |
|||
_name[0] = 0; |
|||
_name[LFS_NAME_MAX] = 0; |
|||
_dir_path = NULL; |
|||
|
|||
_dir = NULL; |
|||
_file = NULL; |
|||
} |
|||
|
|||
File::File (char const *filename, uint8_t mode, Adafruit_LittleFS &fs) |
|||
: File(fs) |
|||
{ |
|||
// public constructor calls public API open(), which will obtain the mutex
|
|||
this->open(filename, mode); |
|||
} |
|||
|
|||
bool File::_open_file (char const *filepath, uint8_t mode) |
|||
{ |
|||
int flags = (mode == FILE_O_READ) ? LFS_O_RDONLY : |
|||
(mode == FILE_O_WRITE) ? (LFS_O_RDWR | LFS_O_CREAT) : 0; |
|||
|
|||
if ( flags ) |
|||
{ |
|||
_file = (lfs_file_t*) malloc(sizeof(lfs_file_t)); |
|||
if (!_file) return false; |
|||
|
|||
int rc = lfs_file_open(_fs->_getFS(), _file, filepath, flags); |
|||
|
|||
if ( rc ) |
|||
{ |
|||
// failed to open
|
|||
PRINT_LFS_ERR(rc); |
|||
// free memory
|
|||
free(_file); |
|||
_file = NULL; |
|||
return false; |
|||
} |
|||
|
|||
// move to end of file
|
|||
if ( mode == FILE_O_WRITE ) lfs_file_seek(_fs->_getFS(), _file, 0, LFS_SEEK_END); |
|||
|
|||
_is_dir = false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
bool File::_open_dir (char const *filepath) |
|||
{ |
|||
_dir = (lfs_dir_t*) malloc(sizeof(lfs_dir_t)); |
|||
if (!_dir) return false; |
|||
|
|||
int rc = lfs_dir_open(_fs->_getFS(), _dir, filepath); |
|||
|
|||
if ( rc ) |
|||
{ |
|||
// failed to open
|
|||
PRINT_LFS_ERR(rc); |
|||
// free memory
|
|||
free(_dir); |
|||
_dir = NULL; |
|||
return false; |
|||
} |
|||
|
|||
_is_dir = true; |
|||
|
|||
_dir_path = (char*) malloc(strlen(filepath) + 1); |
|||
strcpy(_dir_path, filepath); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
bool File::open (char const *filepath, uint8_t mode) |
|||
{ |
|||
bool ret = false; |
|||
_fs->_lockFS(); |
|||
|
|||
ret = this->_open(filepath, mode); |
|||
|
|||
_fs->_unlockFS(); |
|||
return ret; |
|||
} |
|||
|
|||
bool File::_open (char const *filepath, uint8_t mode) |
|||
{ |
|||
bool ret = false; |
|||
|
|||
// close if currently opened
|
|||
if ( this->isOpen() ) _close(); |
|||
|
|||
struct lfs_info info; |
|||
int rc = lfs_stat(_fs->_getFS(), filepath, &info); |
|||
|
|||
if ( LFS_ERR_OK == rc ) |
|||
{ |
|||
// file existed, open file or directory accordingly
|
|||
ret = (info.type == LFS_TYPE_REG) ? _open_file(filepath, mode) : _open_dir(filepath); |
|||
} |
|||
else if ( LFS_ERR_NOENT == rc ) |
|||
{ |
|||
// file not existed, only proceed with FILE_O_WRITE mode
|
|||
if ( mode == FILE_O_WRITE ) ret = _open_file(filepath, mode); |
|||
} |
|||
else |
|||
{ |
|||
PRINT_LFS_ERR(rc); |
|||
} |
|||
|
|||
// save bare file name
|
|||
if (ret) |
|||
{ |
|||
char const* splash = strrchr(filepath, '/'); |
|||
strncpy(_name, splash ? (splash + 1) : filepath, LFS_NAME_MAX); |
|||
} |
|||
return ret; |
|||
} |
|||
|
|||
size_t File::write (uint8_t ch) |
|||
{ |
|||
return write(&ch, 1); |
|||
} |
|||
|
|||
size_t File::write (uint8_t const *buf, size_t size) |
|||
{ |
|||
lfs_ssize_t wrcount = 0; |
|||
_fs->_lockFS(); |
|||
|
|||
if (!this->_is_dir) |
|||
{ |
|||
wrcount = lfs_file_write(_fs->_getFS(), _file, buf, size); |
|||
if (wrcount < 0) |
|||
{ |
|||
wrcount = 0; |
|||
} |
|||
} |
|||
|
|||
_fs->_unlockFS(); |
|||
return wrcount; |
|||
} |
|||
|
|||
int File::read (void) |
|||
{ |
|||
// this thin wrapper relies on called function to synchronize
|
|||
int ret = -1; |
|||
uint8_t ch; |
|||
if (read(&ch, 1) > 0) |
|||
{ |
|||
ret = static_cast<int>(ch); |
|||
} |
|||
return ret; |
|||
} |
|||
|
|||
int File::read (void *buf, uint16_t nbyte) |
|||
{ |
|||
int ret = 0; |
|||
_fs->_lockFS(); |
|||
|
|||
if (!this->_is_dir) |
|||
{ |
|||
ret = lfs_file_read(_fs->_getFS(), _file, buf, nbyte); |
|||
} |
|||
|
|||
_fs->_unlockFS(); |
|||
return ret; |
|||
} |
|||
|
|||
int File::peek (void) |
|||
{ |
|||
int ret = -1; |
|||
_fs->_lockFS(); |
|||
|
|||
if (!this->_is_dir) |
|||
{ |
|||
uint32_t pos = lfs_file_tell(_fs->_getFS(), _file); |
|||
uint8_t ch = 0; |
|||
if (lfs_file_read(_fs->_getFS(), _file, &ch, 1) > 0) |
|||
{ |
|||
ret = static_cast<int>(ch); |
|||
} |
|||
(void) lfs_file_seek(_fs->_getFS(), _file, pos, LFS_SEEK_SET); |
|||
} |
|||
|
|||
_fs->_unlockFS(); |
|||
return ret; |
|||
} |
|||
|
|||
int File::available (void) |
|||
{ |
|||
int ret = 0; |
|||
_fs->_lockFS(); |
|||
|
|||
if (!this->_is_dir) |
|||
{ |
|||
uint32_t size = lfs_file_size(_fs->_getFS(), _file); |
|||
uint32_t pos = lfs_file_tell(_fs->_getFS(), _file); |
|||
ret = size - pos; |
|||
} |
|||
|
|||
_fs->_unlockFS(); |
|||
return ret; |
|||
} |
|||
|
|||
bool File::seek (uint32_t pos) |
|||
{ |
|||
bool ret = false; |
|||
_fs->_lockFS(); |
|||
|
|||
if (!this->_is_dir) |
|||
{ |
|||
ret = lfs_file_seek(_fs->_getFS(), _file, pos, LFS_SEEK_SET) >= 0; |
|||
} |
|||
|
|||
_fs->_unlockFS(); |
|||
return ret; |
|||
} |
|||
|
|||
uint32_t File::position (void) |
|||
{ |
|||
uint32_t ret = 0; |
|||
_fs->_lockFS(); |
|||
|
|||
if (!this->_is_dir) |
|||
{ |
|||
ret = lfs_file_tell(_fs->_getFS(), _file); |
|||
} |
|||
|
|||
_fs->_unlockFS(); |
|||
return ret; |
|||
} |
|||
|
|||
uint32_t File::size (void) |
|||
{ |
|||
uint32_t ret = 0; |
|||
_fs->_lockFS(); |
|||
|
|||
if (!this->_is_dir) |
|||
{ |
|||
ret = lfs_file_size(_fs->_getFS(), _file); |
|||
} |
|||
|
|||
_fs->_unlockFS(); |
|||
return ret; |
|||
} |
|||
|
|||
bool File::truncate (uint32_t pos) |
|||
{ |
|||
int32_t ret=LFS_ERR_ISDIR; |
|||
_fs->_lockFS(); |
|||
if (!this->_is_dir) |
|||
{ |
|||
ret = lfs_file_truncate(_fs->_getFS(), _file, pos); |
|||
} |
|||
_fs->_unlockFS(); |
|||
return ( ret == 0 ); |
|||
} |
|||
|
|||
bool File::truncate (void) |
|||
{ |
|||
int32_t ret=LFS_ERR_ISDIR; |
|||
uint32_t pos; |
|||
_fs->_lockFS(); |
|||
if (!this->_is_dir) |
|||
{ |
|||
pos = lfs_file_tell(_fs->_getFS(), _file); |
|||
ret = lfs_file_truncate(_fs->_getFS(), _file, pos); |
|||
} |
|||
_fs->_unlockFS(); |
|||
return ( ret == 0 ); |
|||
} |
|||
|
|||
void File::flush (void) |
|||
{ |
|||
_fs->_lockFS(); |
|||
|
|||
if (!this->_is_dir) |
|||
{ |
|||
lfs_file_sync(_fs->_getFS(), _file); |
|||
} |
|||
|
|||
_fs->_unlockFS(); |
|||
return; |
|||
} |
|||
|
|||
void File::close (void) |
|||
{ |
|||
_fs->_lockFS(); |
|||
this->_close(); |
|||
_fs->_unlockFS(); |
|||
} |
|||
|
|||
void File::_close(void) |
|||
{ |
|||
if ( this->isOpen() ) |
|||
{ |
|||
if ( this->_is_dir ) |
|||
{ |
|||
lfs_dir_close(_fs->_getFS(), _dir); |
|||
free(_dir); |
|||
_dir = NULL; |
|||
|
|||
if ( this->_dir_path ) free(_dir_path); |
|||
_dir_path = NULL; |
|||
} |
|||
else |
|||
{ |
|||
lfs_file_close(this->_fs->_getFS(), _file); |
|||
free(_file); |
|||
_file = NULL; |
|||
} |
|||
} |
|||
} |
|||
|
|||
File::operator bool (void) |
|||
{ |
|||
return isOpen(); |
|||
} |
|||
|
|||
bool File::isOpen(void) |
|||
{ |
|||
return (_file != NULL) || (_dir != NULL); |
|||
} |
|||
|
|||
// WARNING -- although marked as `const`, the values pointed
|
|||
// to may change. For example, if the same File
|
|||
// object has `open()` called with a different
|
|||
// file or directory name, this same pointer will
|
|||
// suddenly (unexpectedly?) have different values.
|
|||
char const* File::name (void) |
|||
{ |
|||
return this->_name; |
|||
} |
|||
|
|||
bool File::isDirectory (void) |
|||
{ |
|||
return this->_is_dir; |
|||
} |
|||
|
|||
File File::openNextFile (uint8_t mode) |
|||
{ |
|||
_fs->_lockFS(); |
|||
|
|||
File ret(*_fs); |
|||
if (this->_is_dir) |
|||
{ |
|||
struct lfs_info info; |
|||
int rc; |
|||
|
|||
// lfs_dir_read returns 0 when reaching end of directory, 1 if found an entry
|
|||
// Skip the "." and ".." entries ...
|
|||
do |
|||
{ |
|||
rc = lfs_dir_read(_fs->_getFS(), _dir, &info); |
|||
} while ( rc == 1 && (!strcmp(".", info.name) || !strcmp("..", info.name)) ); |
|||
|
|||
if ( rc == 1 ) |
|||
{ |
|||
// string cat name with current folder
|
|||
char filepath[strlen(_dir_path) + 1 + strlen(info.name) + 1]; // potential for significant stack usage
|
|||
strcpy(filepath, _dir_path); |
|||
if ( !(_dir_path[0] == '/' && _dir_path[1] == 0) ) strcat(filepath, "/"); // only add '/' if cwd is not root
|
|||
strcat(filepath, info.name); |
|||
|
|||
(void)ret._open(filepath, mode); // return value is ignored ... caller is expected to check isOpened()
|
|||
} |
|||
else if ( rc < 0 ) |
|||
{ |
|||
PRINT_LFS_ERR(rc); |
|||
} |
|||
} |
|||
_fs->_unlockFS(); |
|||
return ret; |
|||
} |
|||
|
|||
void File::rewindDirectory (void) |
|||
{ |
|||
_fs->_lockFS(); |
|||
if (this->_is_dir) |
|||
{ |
|||
lfs_dir_rewind(_fs->_getFS(), _dir); |
|||
} |
|||
_fs->_unlockFS(); |
|||
} |
|||
|
|||
@ -0,0 +1,108 @@ |
|||
/*
|
|||
* The MIT License (MIT) |
|||
* |
|||
* Copyright (c) 2019 Ha Thach for Adafruit Industries |
|||
* |
|||
* Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
* of this software and associated documentation files (the "Software"), to deal |
|||
* in the Software without restriction, including without limitation the rights |
|||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
* copies of the Software, and to permit persons to whom the Software is |
|||
* furnished to do so, subject to the following conditions: |
|||
* |
|||
* The above copyright notice and this permission notice shall be included in |
|||
* all copies or substantial portions of the Software. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|||
* THE SOFTWARE. |
|||
*/ |
|||
|
|||
#ifndef ADAFRUIT_LITTLEFS_FILE_H_ |
|||
#define ADAFRUIT_LITTLEFS_FILE_H_ |
|||
|
|||
// Forward declaration
|
|||
class Adafruit_LittleFS; |
|||
|
|||
namespace Adafruit_LittleFS_Namespace |
|||
{ |
|||
|
|||
// avoid conflict with other FileSystem FILE_READ/FILE_WRITE
|
|||
enum |
|||
{ |
|||
FILE_O_READ = 0, |
|||
FILE_O_WRITE = 1, |
|||
}; |
|||
|
|||
class File : public Stream |
|||
{ |
|||
public: |
|||
File (Adafruit_LittleFS &fs); |
|||
File (char const *filename, uint8_t mode, Adafruit_LittleFS &fs); |
|||
|
|||
public: |
|||
|
|||
bool open (char const *filename, uint8_t mode); |
|||
|
|||
//------------- Stream API -------------//
|
|||
virtual size_t write (uint8_t ch); |
|||
virtual size_t write (uint8_t const *buf, size_t size); |
|||
size_t write(const char *str) { |
|||
if (str == NULL) return 0; |
|||
return write((const uint8_t *)str, strlen(str)); |
|||
} |
|||
size_t write(const char *buffer, size_t size) { |
|||
return write((const uint8_t *)buffer, size); |
|||
} |
|||
|
|||
virtual int read (void); |
|||
int read (void *buf, uint16_t nbyte); |
|||
|
|||
virtual int peek (void); |
|||
virtual int available (void); |
|||
virtual void flush (void); |
|||
|
|||
bool seek (uint32_t pos); |
|||
uint32_t position (void); |
|||
uint32_t size (void); |
|||
|
|||
bool truncate (uint32_t pos); |
|||
bool truncate (void); |
|||
|
|||
void close (void); |
|||
|
|||
operator bool (void); |
|||
|
|||
bool isOpen(void); |
|||
char const* name (void); |
|||
|
|||
bool isDirectory (void); |
|||
File openNextFile (uint8_t mode = FILE_O_READ); |
|||
void rewindDirectory (void); |
|||
|
|||
private: |
|||
Adafruit_LittleFS* _fs; |
|||
|
|||
bool _is_dir; |
|||
|
|||
union { |
|||
lfs_file_t* _file; |
|||
lfs_dir_t* _dir; |
|||
}; |
|||
|
|||
char* _dir_path; |
|||
char _name[LFS_NAME_MAX+1]; |
|||
|
|||
bool _open(char const *filepath, uint8_t mode); |
|||
bool _open_file(char const *filepath, uint8_t mode); |
|||
bool _open_dir (char const *filepath); |
|||
void _close(void); |
|||
}; |
|||
|
|||
} |
|||
|
|||
#endif /* ADAFRUIT_LITTLEFS_FILE_H_ */ |
|||
@ -0,0 +1,24 @@ |
|||
Copyright (c) 2017, Arm Limited. All rights reserved. |
|||
|
|||
Redistribution and use in source and binary forms, with or without modification, |
|||
are permitted provided that the following conditions are met: |
|||
|
|||
- Redistributions of source code must retain the above copyright notice, this |
|||
list of conditions and the following disclaimer. |
|||
- Redistributions in binary form must reproduce the above copyright notice, this |
|||
list of conditions and the following disclaimer in the documentation and/or |
|||
other materials provided with the distribution. |
|||
- Neither the name of ARM nor the names of its contributors may be used to |
|||
endorse or promote products derived from this software without specific prior |
|||
written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR |
|||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
|||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|||
@ -0,0 +1,177 @@ |
|||
## The little filesystem |
|||
|
|||
A little fail-safe filesystem designed for embedded systems. |
|||
|
|||
``` |
|||
| | | .---._____ |
|||
.-----. | | |
|||
--|o |---| littlefs | |
|||
--| |---| | |
|||
'-----' '----------' |
|||
| | | |
|||
``` |
|||
|
|||
**Bounded RAM/ROM** - The littlefs is designed to work with a limited amount |
|||
of memory. Recursion is avoided and dynamic memory is limited to configurable |
|||
buffers that can be provided statically. |
|||
|
|||
**Power-loss resilient** - The littlefs is designed for systems that may have |
|||
random power failures. The littlefs has strong copy-on-write guarantees and |
|||
storage on disk is always kept in a valid state. |
|||
|
|||
**Wear leveling** - Since the most common form of embedded storage is erodible |
|||
flash memories, littlefs provides a form of dynamic wear leveling for systems |
|||
that can not fit a full flash translation layer. |
|||
|
|||
## Example |
|||
|
|||
Here's a simple example that updates a file named `boot_count` every time |
|||
main runs. The program can be interrupted at any time without losing track |
|||
of how many times it has been booted and without corrupting the filesystem: |
|||
|
|||
``` c |
|||
#include "lfs.h" |
|||
|
|||
// variables used by the filesystem |
|||
lfs_t lfs; |
|||
lfs_file_t file; |
|||
|
|||
// configuration of the filesystem is provided by this struct |
|||
const struct lfs_config cfg = { |
|||
// block device operations |
|||
.read = user_provided_block_device_read, |
|||
.prog = user_provided_block_device_prog, |
|||
.erase = user_provided_block_device_erase, |
|||
.sync = user_provided_block_device_sync, |
|||
|
|||
// block device configuration |
|||
.read_size = 16, |
|||
.prog_size = 16, |
|||
.block_size = 4096, |
|||
.block_count = 128, |
|||
.lookahead = 128, |
|||
}; |
|||
|
|||
// entry point |
|||
int main(void) { |
|||
// mount the filesystem |
|||
int err = lfs_mount(&lfs, &cfg); |
|||
|
|||
// reformat if we can't mount the filesystem |
|||
// this should only happen on the first boot |
|||
if (err) { |
|||
lfs_format(&lfs, &cfg); |
|||
lfs_mount(&lfs, &cfg); |
|||
} |
|||
|
|||
// read current count |
|||
uint32_t boot_count = 0; |
|||
lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT); |
|||
lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count)); |
|||
|
|||
// update boot count |
|||
boot_count += 1; |
|||
lfs_file_rewind(&lfs, &file); |
|||
lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count)); |
|||
|
|||
// remember the storage is not updated until the file is closed successfully |
|||
lfs_file_close(&lfs, &file); |
|||
|
|||
// release any resources we were using |
|||
lfs_unmount(&lfs); |
|||
|
|||
// print the boot count |
|||
printf("boot_count: %d\n", boot_count); |
|||
} |
|||
``` |
|||
|
|||
## Usage |
|||
|
|||
Detailed documentation (or at least as much detail as is currently available) |
|||
can be found in the comments in [lfs.h](lfs.h). |
|||
|
|||
As you may have noticed, littlefs takes in a configuration structure that |
|||
defines how the filesystem operates. The configuration struct provides the |
|||
filesystem with the block device operations and dimensions, tweakable |
|||
parameters that tradeoff memory usage for performance, and optional |
|||
static buffers if the user wants to avoid dynamic memory. |
|||
|
|||
The state of the littlefs is stored in the `lfs_t` type which is left up |
|||
to the user to allocate, allowing multiple filesystems to be in use |
|||
simultaneously. With the `lfs_t` and configuration struct, a user can |
|||
format a block device or mount the filesystem. |
|||
|
|||
Once mounted, the littlefs provides a full set of POSIX-like file and |
|||
directory functions, with the deviation that the allocation of filesystem |
|||
structures must be provided by the user. |
|||
|
|||
All POSIX operations, such as remove and rename, are atomic, even in event |
|||
of power-loss. Additionally, no file updates are actually committed to the |
|||
filesystem until sync or close is called on the file. |
|||
|
|||
## Other notes |
|||
|
|||
All littlefs have the potential to return a negative error code. The errors |
|||
can be either one of those found in the `enum lfs_error` in [lfs.h](lfs.h), |
|||
or an error returned by the user's block device operations. |
|||
|
|||
In the configuration struct, the `prog` and `erase` function provided by the |
|||
user may return a `LFS_ERR_CORRUPT` error if the implementation already can |
|||
detect corrupt blocks. However, the wear leveling does not depend on the return |
|||
code of these functions, instead all data is read back and checked for |
|||
integrity. |
|||
|
|||
If your storage caches writes, make sure that the provided `sync` function |
|||
flushes all the data to memory and ensures that the next read fetches the data |
|||
from memory, otherwise data integrity can not be guaranteed. If the `write` |
|||
function does not perform caching, and therefore each `read` or `write` call |
|||
hits the memory, the `sync` function can simply return 0. |
|||
|
|||
## Reference material |
|||
|
|||
[DESIGN.md](DESIGN.md) - DESIGN.md contains a fully detailed dive into how |
|||
littlefs actually works. I would encourage you to read it since the |
|||
solutions and tradeoffs at work here are quite interesting. |
|||
|
|||
[SPEC.md](SPEC.md) - SPEC.md contains the on-disk specification of littlefs |
|||
with all the nitty-gritty details. Can be useful for developing tooling. |
|||
|
|||
## Testing |
|||
|
|||
The littlefs comes with a test suite designed to run on a PC using the |
|||
[emulated block device](emubd/lfs_emubd.h) found in the emubd directory. |
|||
The tests assume a Linux environment and can be started with make: |
|||
|
|||
``` bash |
|||
make test |
|||
``` |
|||
|
|||
## License |
|||
|
|||
The littlefs is provided under the [BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html) |
|||
license. See [LICENSE.md](LICENSE.md) for more information. Contributions to |
|||
this project are accepted under the same license. |
|||
|
|||
Individual files contain the following tag instead of the full license text. |
|||
|
|||
SPDX-License-Identifier: BSD-3-Clause |
|||
|
|||
This enables machine processing of license information based on the SPDX |
|||
License Identifiers that are here available: http://spdx.org/licenses/ |
|||
|
|||
## Related projects |
|||
|
|||
[Mbed OS](https://github.com/ARMmbed/mbed-os/tree/master/features/filesystem/littlefs) - |
|||
The easiest way to get started with littlefs is to jump into [Mbed](https://os.mbed.com/), |
|||
which already has block device drivers for most forms of embedded storage. The |
|||
littlefs is available in Mbed OS as the [LittleFileSystem](https://os.mbed.com/docs/latest/reference/littlefilesystem.html) |
|||
class. |
|||
|
|||
[littlefs-fuse](https://github.com/geky/littlefs-fuse) - A [FUSE](https://github.com/libfuse/libfuse) |
|||
wrapper for littlefs. The project allows you to mount littlefs directly on a |
|||
Linux machine. Can be useful for debugging littlefs if you have an SD card |
|||
handy. |
|||
|
|||
[littlefs-js](https://github.com/geky/littlefs-js) - A javascript wrapper for |
|||
littlefs. I'm not sure why you would want this, but it is handy for demos. |
|||
You can see it in action [here](http://littlefs.geky.net/demo.html). |
|||
File diff suppressed because it is too large
@ -0,0 +1,501 @@ |
|||
/*
|
|||
* The little filesystem |
|||
* |
|||
* Copyright (c) 2017, Arm Limited. All rights reserved. |
|||
* SPDX-License-Identifier: BSD-3-Clause |
|||
*/ |
|||
#ifndef LFS_H |
|||
#define LFS_H |
|||
|
|||
#include <stdint.h> |
|||
#include <stdbool.h> |
|||
|
|||
#ifdef __cplusplus |
|||
extern "C" |
|||
{ |
|||
#endif |
|||
|
|||
|
|||
/// Version info ///
|
|||
|
|||
// Software library version
|
|||
// Major (top-nibble), incremented on backwards incompatible changes
|
|||
// Minor (bottom-nibble), incremented on feature additions
|
|||
#define LFS_VERSION 0x00010007 |
|||
#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) |
|||
#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) |
|||
|
|||
// Version of On-disk data structures
|
|||
// Major (top-nibble), incremented on backwards incompatible changes
|
|||
// Minor (bottom-nibble), incremented on feature additions
|
|||
#define LFS_DISK_VERSION 0x00010001 |
|||
#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16)) |
|||
#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0)) |
|||
|
|||
|
|||
/// Definitions ///
|
|||
|
|||
// Type definitions
|
|||
typedef uint32_t lfs_size_t; |
|||
typedef uint32_t lfs_off_t; |
|||
|
|||
typedef int32_t lfs_ssize_t; |
|||
typedef int32_t lfs_soff_t; |
|||
|
|||
typedef uint32_t lfs_block_t; |
|||
|
|||
// Max name size in bytes
|
|||
#ifndef LFS_NAME_MAX |
|||
#define LFS_NAME_MAX 255 |
|||
#endif |
|||
|
|||
// Max file size in bytes
|
|||
#ifndef LFS_FILE_MAX |
|||
#define LFS_FILE_MAX 2147483647 |
|||
#endif |
|||
|
|||
// Possible error codes, these are negative to allow
|
|||
// valid positive return values
|
|||
enum lfs_error { |
|||
LFS_ERR_OK = 0, // No error
|
|||
LFS_ERR_IO = -5, // Error during device operation
|
|||
LFS_ERR_CORRUPT = -52, // Corrupted
|
|||
LFS_ERR_NOENT = -2, // No directory entry
|
|||
LFS_ERR_EXIST = -17, // Entry already exists
|
|||
LFS_ERR_NOTDIR = -20, // Entry is not a dir
|
|||
LFS_ERR_ISDIR = -21, // Entry is a dir
|
|||
LFS_ERR_NOTEMPTY = -39, // Dir is not empty
|
|||
LFS_ERR_BADF = -9, // Bad file number
|
|||
LFS_ERR_FBIG = -27, // File too large
|
|||
LFS_ERR_INVAL = -22, // Invalid parameter
|
|||
LFS_ERR_NOSPC = -28, // No space left on device
|
|||
LFS_ERR_NOMEM = -12, // No more memory available
|
|||
}; |
|||
|
|||
// File types
|
|||
enum lfs_type { |
|||
LFS_TYPE_REG = 0x11, |
|||
LFS_TYPE_DIR = 0x22, |
|||
LFS_TYPE_SUPERBLOCK = 0x2e, |
|||
}; |
|||
|
|||
// File open flags
|
|||
enum lfs_open_flags { |
|||
// open flags
|
|||
LFS_O_RDONLY = 1, // Open a file as read only
|
|||
LFS_O_WRONLY = 2, // Open a file as write only
|
|||
LFS_O_RDWR = 3, // Open a file as read and write
|
|||
LFS_O_CREAT = 0x0100, // Create a file if it does not exist
|
|||
LFS_O_EXCL = 0x0200, // Fail if a file already exists
|
|||
LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size
|
|||
LFS_O_APPEND = 0x0800, // Move to end of file on every write
|
|||
|
|||
// internally used flags
|
|||
LFS_F_DIRTY = 0x10000, // File does not match storage
|
|||
LFS_F_WRITING = 0x20000, // File has been written since last flush
|
|||
LFS_F_READING = 0x40000, // File has been read since last flush
|
|||
LFS_F_ERRED = 0x80000, // An error occured during write
|
|||
}; |
|||
|
|||
// File seek flags
|
|||
enum lfs_whence_flags { |
|||
LFS_SEEK_SET = 0, // Seek relative to an absolute position
|
|||
LFS_SEEK_CUR = 1, // Seek relative to the current file position
|
|||
LFS_SEEK_END = 2, // Seek relative to the end of the file
|
|||
}; |
|||
|
|||
|
|||
// Configuration provided during initialization of the littlefs
|
|||
struct lfs_config { |
|||
// Opaque user provided context that can be used to pass
|
|||
// information to the block device operations
|
|||
void *context; |
|||
|
|||
// Read a region in a block. Negative error codes are propogated
|
|||
// to the user.
|
|||
int (*read)(const struct lfs_config *c, lfs_block_t block, |
|||
lfs_off_t off, void *buffer, lfs_size_t size); |
|||
|
|||
// Program a region in a block. The block must have previously
|
|||
// been erased. Negative error codes are propogated to the user.
|
|||
// May return LFS_ERR_CORRUPT if the block should be considered bad.
|
|||
int (*prog)(const struct lfs_config *c, lfs_block_t block, |
|||
lfs_off_t off, const void *buffer, lfs_size_t size); |
|||
|
|||
// Erase a block. A block must be erased before being programmed.
|
|||
// The state of an erased block is undefined. Negative error codes
|
|||
// are propogated to the user.
|
|||
// May return LFS_ERR_CORRUPT if the block should be considered bad.
|
|||
int (*erase)(const struct lfs_config *c, lfs_block_t block); |
|||
|
|||
// Sync the state of the underlying block device. Negative error codes
|
|||
// are propogated to the user.
|
|||
int (*sync)(const struct lfs_config *c); |
|||
|
|||
// Minimum size of a block read. This determines the size of read buffers.
|
|||
// This may be larger than the physical read size to improve performance
|
|||
// by caching more of the block device.
|
|||
lfs_size_t read_size; |
|||
|
|||
// Minimum size of a block program. This determines the size of program
|
|||
// buffers. This may be larger than the physical program size to improve
|
|||
// performance by caching more of the block device.
|
|||
// Must be a multiple of the read size.
|
|||
lfs_size_t prog_size; |
|||
|
|||
// Size of an erasable block. This does not impact ram consumption and
|
|||
// may be larger than the physical erase size. However, this should be
|
|||
// kept small as each file currently takes up an entire block.
|
|||
// Must be a multiple of the program size.
|
|||
lfs_size_t block_size; |
|||
|
|||
// Number of erasable blocks on the device.
|
|||
lfs_size_t block_count; |
|||
|
|||
// Number of blocks to lookahead during block allocation. A larger
|
|||
// lookahead reduces the number of passes required to allocate a block.
|
|||
// The lookahead buffer requires only 1 bit per block so it can be quite
|
|||
// large with little ram impact. Should be a multiple of 32.
|
|||
lfs_size_t lookahead; |
|||
|
|||
// Optional, statically allocated read buffer. Must be read sized.
|
|||
void *read_buffer; |
|||
|
|||
// Optional, statically allocated program buffer. Must be program sized.
|
|||
void *prog_buffer; |
|||
|
|||
// Optional, statically allocated lookahead buffer. Must be 1 bit per
|
|||
// lookahead block.
|
|||
void *lookahead_buffer; |
|||
|
|||
// Optional, statically allocated buffer for files. Must be program sized.
|
|||
// If enabled, only one file may be opened at a time.
|
|||
void *file_buffer; |
|||
}; |
|||
|
|||
// Optional configuration provided during lfs_file_opencfg
|
|||
struct lfs_file_config { |
|||
// Optional, statically allocated buffer for files. Must be program sized.
|
|||
// If NULL, malloc will be used by default.
|
|||
void *buffer; |
|||
}; |
|||
|
|||
// File info structure
|
|||
struct lfs_info { |
|||
// Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR
|
|||
uint8_t type; |
|||
|
|||
// Size of the file, only valid for REG files
|
|||
lfs_size_t size; |
|||
|
|||
// Name of the file stored as a null-terminated string
|
|||
char name[LFS_NAME_MAX+1]; |
|||
}; |
|||
|
|||
|
|||
/// littlefs data structures ///
|
|||
typedef struct lfs_entry { |
|||
lfs_off_t off; |
|||
|
|||
struct lfs_disk_entry { |
|||
uint8_t type; |
|||
uint8_t elen; |
|||
uint8_t alen; |
|||
uint8_t nlen; |
|||
union { |
|||
struct { |
|||
lfs_block_t head; |
|||
lfs_size_t size; |
|||
} file; |
|||
lfs_block_t dir[2]; |
|||
} u; |
|||
} d; |
|||
} lfs_entry_t; |
|||
|
|||
typedef struct lfs_cache { |
|||
lfs_block_t block; |
|||
lfs_off_t off; |
|||
uint8_t *buffer; |
|||
} lfs_cache_t; |
|||
|
|||
typedef struct lfs_file { |
|||
struct lfs_file *next; |
|||
lfs_block_t pair[2]; |
|||
lfs_off_t poff; |
|||
|
|||
lfs_block_t head; |
|||
lfs_size_t size; |
|||
|
|||
const struct lfs_file_config *cfg; |
|||
uint32_t flags; |
|||
lfs_off_t pos; |
|||
lfs_block_t block; |
|||
lfs_off_t off; |
|||
lfs_cache_t cache; |
|||
} lfs_file_t; |
|||
|
|||
typedef struct lfs_dir { |
|||
struct lfs_dir *next; |
|||
lfs_block_t pair[2]; |
|||
lfs_off_t off; |
|||
|
|||
lfs_block_t head[2]; |
|||
lfs_off_t pos; |
|||
|
|||
struct lfs_disk_dir { |
|||
uint32_t rev; |
|||
lfs_size_t size; |
|||
lfs_block_t tail[2]; |
|||
} d; |
|||
} lfs_dir_t; |
|||
|
|||
typedef struct lfs_superblock { |
|||
lfs_off_t off; |
|||
|
|||
struct lfs_disk_superblock { |
|||
uint8_t type; |
|||
uint8_t elen; |
|||
uint8_t alen; |
|||
uint8_t nlen; |
|||
lfs_block_t root[2]; |
|||
uint32_t block_size; |
|||
uint32_t block_count; |
|||
uint32_t version; |
|||
char magic[8]; |
|||
} d; |
|||
} lfs_superblock_t; |
|||
|
|||
typedef struct lfs_free { |
|||
lfs_block_t off; |
|||
lfs_block_t size; |
|||
lfs_block_t i; |
|||
lfs_block_t ack; |
|||
uint32_t *buffer; |
|||
} lfs_free_t; |
|||
|
|||
// The littlefs type
|
|||
typedef struct lfs { |
|||
const struct lfs_config *cfg; |
|||
|
|||
lfs_block_t root[2]; |
|||
lfs_file_t *files; |
|||
lfs_dir_t *dirs; |
|||
|
|||
lfs_cache_t rcache; |
|||
lfs_cache_t pcache; |
|||
|
|||
lfs_free_t free; |
|||
bool deorphaned; |
|||
bool moving; |
|||
} lfs_t; |
|||
|
|||
|
|||
/// Filesystem functions ///
|
|||
|
|||
// Format a block device with the littlefs
|
|||
//
|
|||
// Requires a littlefs object and config struct. This clobbers the littlefs
|
|||
// object, and does not leave the filesystem mounted. The config struct must
|
|||
// be zeroed for defaults and backwards compatibility.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_format(lfs_t *lfs, const struct lfs_config *config); |
|||
|
|||
// Mounts a littlefs
|
|||
//
|
|||
// Requires a littlefs object and config struct. Multiple filesystems
|
|||
// may be mounted simultaneously with multiple littlefs objects. Both
|
|||
// lfs and config must be allocated while mounted. The config struct must
|
|||
// be zeroed for defaults and backwards compatibility.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_mount(lfs_t *lfs, const struct lfs_config *config); |
|||
|
|||
// Unmounts a littlefs
|
|||
//
|
|||
// Does nothing besides releasing any allocated resources.
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_unmount(lfs_t *lfs); |
|||
|
|||
/// General operations ///
|
|||
|
|||
// Removes a file or directory
|
|||
//
|
|||
// If removing a directory, the directory must be empty.
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_remove(lfs_t *lfs, const char *path); |
|||
|
|||
// Rename or move a file or directory
|
|||
//
|
|||
// If the destination exists, it must match the source in type.
|
|||
// If the destination is a directory, the directory must be empty.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath); |
|||
|
|||
// Find info about a file or directory
|
|||
//
|
|||
// Fills out the info structure, based on the specified file or directory.
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info); |
|||
|
|||
|
|||
/// File operations ///
|
|||
|
|||
// Open a file
|
|||
//
|
|||
// The mode that the file is opened in is determined by the flags, which
|
|||
// are values from the enum lfs_open_flags that are bitwise-ored together.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_file_open(lfs_t *lfs, lfs_file_t *file, |
|||
const char *path, int flags); |
|||
|
|||
// Open a file with extra configuration
|
|||
//
|
|||
// The mode that the file is opened in is determined by the flags, which
|
|||
// are values from the enum lfs_open_flags that are bitwise-ored together.
|
|||
//
|
|||
// The config struct provides additional config options per file as described
|
|||
// above. The config struct must be allocated while the file is open, and the
|
|||
// config struct must be zeroed for defaults and backwards compatibility.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, |
|||
const char *path, int flags, |
|||
const struct lfs_file_config *config); |
|||
|
|||
// Close a file
|
|||
//
|
|||
// Any pending writes are written out to storage as though
|
|||
// sync had been called and releases any allocated resources.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_file_close(lfs_t *lfs, lfs_file_t *file); |
|||
|
|||
// Synchronize a file on storage
|
|||
//
|
|||
// Any pending writes are written out to storage.
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_file_sync(lfs_t *lfs, lfs_file_t *file); |
|||
|
|||
// Read data from file
|
|||
//
|
|||
// Takes a buffer and size indicating where to store the read data.
|
|||
// Returns the number of bytes read, or a negative error code on failure.
|
|||
lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, |
|||
void *buffer, lfs_size_t size); |
|||
|
|||
// Write data to file
|
|||
//
|
|||
// Takes a buffer and size indicating the data to write. The file will not
|
|||
// actually be updated on the storage until either sync or close is called.
|
|||
//
|
|||
// Returns the number of bytes written, or a negative error code on failure.
|
|||
lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, |
|||
const void *buffer, lfs_size_t size); |
|||
|
|||
// Change the position of the file
|
|||
//
|
|||
// The change in position is determined by the offset and whence flag.
|
|||
// Returns the old position of the file, or a negative error code on failure.
|
|||
lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, |
|||
lfs_soff_t off, int whence); |
|||
|
|||
// Truncates the size of the file to the specified size
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size); |
|||
|
|||
// Return the position of the file
|
|||
//
|
|||
// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR)
|
|||
// Returns the position of the file, or a negative error code on failure.
|
|||
lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file); |
|||
|
|||
// Change the position of the file to the beginning of the file
|
|||
//
|
|||
// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR)
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); |
|||
|
|||
// Return the size of the file
|
|||
//
|
|||
// Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END)
|
|||
// Returns the size of the file, or a negative error code on failure.
|
|||
lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); |
|||
|
|||
|
|||
/// Directory operations ///
|
|||
|
|||
// Create a directory
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_mkdir(lfs_t *lfs, const char *path); |
|||
|
|||
// Open a directory
|
|||
//
|
|||
// Once open a directory can be used with read to iterate over files.
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path); |
|||
|
|||
// Close a directory
|
|||
//
|
|||
// Releases any allocated resources.
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir); |
|||
|
|||
// Read an entry in the directory
|
|||
//
|
|||
// Fills out the info structure, based on the specified file or directory.
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info); |
|||
|
|||
// Change the position of the directory
|
|||
//
|
|||
// The new off must be a value previous returned from tell and specifies
|
|||
// an absolute offset in the directory seek.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off); |
|||
|
|||
// Return the position of the directory
|
|||
//
|
|||
// The returned offset is only meant to be consumed by seek and may not make
|
|||
// sense, but does indicate the current position in the directory iteration.
|
|||
//
|
|||
// Returns the position of the directory, or a negative error code on failure.
|
|||
lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir); |
|||
|
|||
// Change the position of the directory to the beginning of the directory
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir); |
|||
|
|||
|
|||
/// Miscellaneous littlefs specific operations ///
|
|||
|
|||
// Traverse through all blocks in use by the filesystem
|
|||
//
|
|||
// The provided callback will be called with each block address that is
|
|||
// currently in use by the filesystem. This can be used to determine which
|
|||
// blocks are in use or how much of the storage is available.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); |
|||
|
|||
// Prunes any recoverable errors that may have occured in the filesystem
|
|||
//
|
|||
// Not needed to be called by user unless an operation is interrupted
|
|||
// but the filesystem is still mounted. This is already called on first
|
|||
// allocation.
|
|||
//
|
|||
// Returns a negative error code on failure.
|
|||
int lfs_deorphan(lfs_t *lfs); |
|||
|
|||
|
|||
#ifdef __cplusplus |
|||
} /* extern "C" */ |
|||
#endif |
|||
|
|||
#endif |
|||
@ -0,0 +1,31 @@ |
|||
/*
|
|||
* lfs util functions |
|||
* |
|||
* Copyright (c) 2017, Arm Limited. All rights reserved. |
|||
* SPDX-License-Identifier: BSD-3-Clause |
|||
*/ |
|||
#include "lfs_util.h" |
|||
|
|||
// Only compile if user does not provide custom config
|
|||
#ifndef LFS_CONFIG |
|||
|
|||
|
|||
// Software CRC implementation with small lookup table
|
|||
void lfs_crc(uint32_t *restrict crc, const void *buffer, size_t size) { |
|||
static const uint32_t rtable[16] = { |
|||
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, |
|||
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, |
|||
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, |
|||
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, |
|||
}; |
|||
|
|||
const uint8_t *data = buffer; |
|||
|
|||
for (size_t i = 0; i < size; i++) { |
|||
*crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 0)) & 0xf]; |
|||
*crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 4)) & 0xf]; |
|||
} |
|||
} |
|||
|
|||
|
|||
#endif |
|||
@ -0,0 +1,197 @@ |
|||
/*
|
|||
* lfs utility functions |
|||
* |
|||
* Copyright (c) 2017, Arm Limited. All rights reserved. |
|||
* SPDX-License-Identifier: BSD-3-Clause |
|||
*/ |
|||
#ifndef LFS_UTIL_H |
|||
#define LFS_UTIL_H |
|||
|
|||
// Users can override lfs_util.h with their own configuration by defining
|
|||
// LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h).
|
|||
//
|
|||
// If LFS_CONFIG is used, none of the default utils will be emitted and must be
|
|||
// provided by the config file. To start I would suggest copying lfs_util.h and
|
|||
// modifying as needed.
|
|||
#ifdef LFS_CONFIG |
|||
#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x) |
|||
#define LFS_STRINGIZE2(x) #x |
|||
#include LFS_STRINGIZE(LFS_CONFIG) |
|||
#else |
|||
|
|||
// System includes
|
|||
#include <stdint.h> |
|||
#include <stdbool.h> |
|||
#include <string.h> |
|||
|
|||
#ifndef LFS_NO_MALLOC |
|||
#include <stdlib.h> |
|||
#endif |
|||
#ifndef LFS_NO_ASSERT |
|||
#include <assert.h> |
|||
#endif |
|||
|
|||
#if !CFG_DEBUG |
|||
#define LFS_NO_DEBUG |
|||
#define LFS_NO_WARN |
|||
#define LFS_NO_ERROR |
|||
#endif |
|||
|
|||
#if !defined(LFS_NO_DEBUG) || !defined(LFS_NO_WARN) || !defined(LFS_NO_ERROR) |
|||
#include <stdio.h> |
|||
#endif |
|||
|
|||
#ifdef __cplusplus |
|||
extern "C" |
|||
{ |
|||
#endif |
|||
|
|||
|
|||
// Macros, may be replaced by system specific wrappers. Arguments to these
|
|||
// macros must not have side-effects as the macros can be removed for a smaller
|
|||
// code footprint
|
|||
|
|||
// Logging functions
|
|||
#ifndef LFS_NO_DEBUG |
|||
#define LFS_DEBUG(fmt, ...) \ |
|||
printf("lfs debug:%d: " fmt "\n", __LINE__, __VA_ARGS__) |
|||
#else |
|||
#define LFS_DEBUG(fmt, ...) |
|||
#endif |
|||
|
|||
#ifndef LFS_NO_WARN |
|||
#define LFS_WARN(fmt, ...) \ |
|||
printf("lfs warn:%d: " fmt "\n", __LINE__, __VA_ARGS__) |
|||
#else |
|||
#define LFS_WARN(fmt, ...) |
|||
#endif |
|||
|
|||
#ifndef LFS_NO_ERROR |
|||
#define LFS_ERROR(fmt, ...) \ |
|||
printf("lfs error:%d: " fmt "\n", __LINE__, __VA_ARGS__) |
|||
#else |
|||
#define LFS_ERROR(fmt, ...) |
|||
#endif |
|||
|
|||
// Runtime assertions
|
|||
#ifndef LFS_NO_ASSERT |
|||
#define LFS_ASSERT(test) assert(test) |
|||
#else |
|||
#define LFS_ASSERT(test) |
|||
#endif |
|||
|
|||
|
|||
// Builtin functions, these may be replaced by more efficient
|
|||
// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more
|
|||
// expensive basic C implementation for debugging purposes
|
|||
|
|||
// Min/max functions for unsigned 32-bit numbers
|
|||
static inline uint32_t lfs_max(uint32_t a, uint32_t b) { |
|||
return (a > b) ? a : b; |
|||
} |
|||
|
|||
static inline uint32_t lfs_min(uint32_t a, uint32_t b) { |
|||
return (a < b) ? a : b; |
|||
} |
|||
|
|||
// Find the next smallest power of 2 less than or equal to a
|
|||
static inline uint32_t lfs_npw2(uint32_t a) { |
|||
#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) |
|||
return 32 - __builtin_clz(a-1); |
|||
#else |
|||
uint32_t r = 0; |
|||
uint32_t s; |
|||
a -= 1; |
|||
s = (a > 0xffff) << 4; a >>= s; r |= s; |
|||
s = (a > 0xff ) << 3; a >>= s; r |= s; |
|||
s = (a > 0xf ) << 2; a >>= s; r |= s; |
|||
s = (a > 0x3 ) << 1; a >>= s; r |= s; |
|||
return (r | (a >> 1)) + 1; |
|||
#endif |
|||
} |
|||
|
|||
// Count the number of trailing binary zeros in a
|
|||
// lfs_ctz(0) may be undefined
|
|||
static inline uint32_t lfs_ctz(uint32_t a) { |
|||
#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) |
|||
return __builtin_ctz(a); |
|||
#else |
|||
return lfs_npw2((a & -a) + 1) - 1; |
|||
#endif |
|||
} |
|||
|
|||
// Count the number of binary ones in a
|
|||
static inline uint32_t lfs_popc(uint32_t a) { |
|||
#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) |
|||
return __builtin_popcount(a); |
|||
#else |
|||
a = a - ((a >> 1) & 0x55555555); |
|||
a = (a & 0x33333333) + ((a >> 2) & 0x33333333); |
|||
return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; |
|||
#endif |
|||
} |
|||
|
|||
// Find the sequence comparison of a and b, this is the distance
|
|||
// between a and b ignoring overflow
|
|||
static inline int lfs_scmp(uint32_t a, uint32_t b) { |
|||
return (int)(unsigned)(a - b); |
|||
} |
|||
|
|||
// Convert from 32-bit little-endian to native order
|
|||
static inline uint32_t lfs_fromle32(uint32_t a) { |
|||
#if !defined(LFS_NO_INTRINSICS) && ( \ |
|||
(defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) |
|||
return a; |
|||
#elif !defined(LFS_NO_INTRINSICS) && ( \ |
|||
(defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ |
|||
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) |
|||
return __builtin_bswap32(a); |
|||
#else |
|||
return (((uint8_t*)&a)[0] << 0) | |
|||
(((uint8_t*)&a)[1] << 8) | |
|||
(((uint8_t*)&a)[2] << 16) | |
|||
(((uint8_t*)&a)[3] << 24); |
|||
#endif |
|||
} |
|||
|
|||
// Convert to 32-bit little-endian from native order
|
|||
static inline uint32_t lfs_tole32(uint32_t a) { |
|||
return lfs_fromle32(a); |
|||
} |
|||
|
|||
// Calculate CRC-32 with polynomial = 0x04c11db7
|
|||
void lfs_crc(uint32_t *crc, const void *buffer, size_t size); |
|||
|
|||
// Allocate memory, only used if buffers are not provided to littlefs
|
|||
static inline void *lfs_malloc(size_t size) { |
|||
#ifndef LFS_NO_MALLOC |
|||
//extern void *pvPortMalloc( size_t xWantedSize );
|
|||
//return pvPortMalloc(size);
|
|||
return malloc(size); |
|||
#else |
|||
(void)size; |
|||
return NULL; |
|||
#endif |
|||
} |
|||
|
|||
// Deallocate memory, only used if buffers are not provided to littlefs
|
|||
static inline void lfs_free(void *p) { |
|||
#ifndef LFS_NO_MALLOC |
|||
//extern void vPortFree( void *pv );
|
|||
//vPortFree(p);
|
|||
free(p); |
|||
#else |
|||
(void)p; |
|||
#endif |
|||
} |
|||
|
|||
|
|||
#ifdef __cplusplus |
|||
} /* extern "C" */ |
|||
#endif |
|||
|
|||
#endif |
|||
#endif |
|||
@ -0,0 +1,10 @@ |
|||
Import("env") |
|||
|
|||
# Make custom HEX from ELF |
|||
env.AddPostAction( |
|||
"$BUILD_DIR/${PROGNAME}.elf", |
|||
env.VerboseAction(" ".join([ |
|||
"$OBJCOPY", "-O", "ihex", "-R", ".eeprom", |
|||
'"$BUILD_DIR/${PROGNAME}.elf"', '"$BUILD_DIR/${PROGNAME}.hex"' |
|||
]), "Building $BUILD_DIR/${PROGNAME}.hex") |
|||
) |
|||
@ -0,0 +1,72 @@ |
|||
{ |
|||
"build": { |
|||
"arduino": { |
|||
"ldscript": "nrf52840_s140_v6.ld" |
|||
}, |
|||
"core": "nRF5", |
|||
"cpu": "cortex-m4", |
|||
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", |
|||
"f_cpu": "64000000L", |
|||
"hwids": [ |
|||
[ |
|||
"0x239A", |
|||
"0x8029" |
|||
], |
|||
[ |
|||
"0x239A", |
|||
"0x0029" |
|||
], |
|||
[ |
|||
"0x239A", |
|||
"0x002A" |
|||
], |
|||
[ |
|||
"0x239A", |
|||
"0x802A" |
|||
] |
|||
], |
|||
"usb_product": "BQ nRF52840", |
|||
"mcu": "nrf52840", |
|||
"variant": "nano-g2-ultra", |
|||
"bsp": { |
|||
"name": "adafruit" |
|||
}, |
|||
"softdevice": { |
|||
"sd_flags": "-DS140", |
|||
"sd_name": "s140", |
|||
"sd_version": "6.1.1", |
|||
"sd_fwid": "0x00B6" |
|||
}, |
|||
"bootloader": { |
|||
"settings_addr": "0xFF000" |
|||
} |
|||
}, |
|||
"connectivity": [ |
|||
"bluetooth" |
|||
], |
|||
"debug": { |
|||
"jlink_device": "nRF52840_xxAA", |
|||
"svd_path": "nrf52840.svd" |
|||
}, |
|||
"frameworks": [ |
|||
"arduino" |
|||
], |
|||
"name": "BQ nRF52840", |
|||
"upload": { |
|||
"maximum_ram_size": 248832, |
|||
"maximum_size": 815104, |
|||
"speed": 115200, |
|||
"protocol": "nrfutil", |
|||
"protocols": [ |
|||
"jlink", |
|||
"nrfjprog", |
|||
"nrfutil", |
|||
"stlink" |
|||
], |
|||
"use_1200bps_touch": true, |
|||
"require_upload_port": true, |
|||
"wait_for_upload_port": true |
|||
}, |
|||
"url": "https://wiki.uniteng.com/en/meshtastic/nano-g2-ultra", |
|||
"vendor": "BQ Consulting" |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
{ |
|||
"build": { |
|||
"arduino": { |
|||
"variant_h": "variant_RAK3172_MODULE.h" |
|||
}, |
|||
"core": "stm32", |
|||
"cpu": "cortex-m4", |
|||
"extra_flags": "-DSTM32WL -DSTM32WLxx -DSTM32WLE5xx", |
|||
"framework_extra_flags": { |
|||
"arduino": "-DUSE_CM4_STARTUP_FILE -DARDUINO_RAK3172_MODULE" |
|||
}, |
|||
"f_cpu": "48000000L", |
|||
"mcu": "stm32wle5ccu", |
|||
"product_line": "STM32WLE5xx", |
|||
"variant": "STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U" |
|||
}, |
|||
"debug": { |
|||
"default_tools": ["stlink"], |
|||
"jlink_device": "STM32WLE5CC", |
|||
"openocd_target": "stm32wlx", |
|||
"svd_path": "STM32WLE5_CM4.svd" |
|||
}, |
|||
"frameworks": ["arduino"], |
|||
"name": "BB-STM32WL", |
|||
"upload": { |
|||
"maximum_ram_size": 65536, |
|||
"maximum_size": 262144, |
|||
"protocol": "stlink", |
|||
"protocols": ["stlink", "jlink"] |
|||
}, |
|||
"url": "https://store.rakwireless.com/products/wisduo-lpwan-module-rak3172", |
|||
"vendor": "RAK" |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
{ |
|||
"build": { |
|||
"arduino": { |
|||
"ldscript": "nrf52840_s140_v6.ld" |
|||
}, |
|||
"core": "nRF5", |
|||
"cpu": "cortex-m4", |
|||
"extra_flags": "-DARDUINO_NRF52840_TTGO_EINK -DNRF52840_XXAA", |
|||
"f_cpu": "64000000L", |
|||
"hwids": [ |
|||
[ |
|||
"0x239A", |
|||
"0x4405" |
|||
], |
|||
[ |
|||
"0x239A", |
|||
"0x0029" |
|||
], |
|||
[ |
|||
"0x239A", |
|||
"0x002A" |
|||
] |
|||
], |
|||
"usb_product": "elecrow_eink", |
|||
"mcu": "nrf52840", |
|||
"variant": "ELECROW-ThinkNode-M1", |
|||
"bsp": { |
|||
"name": "adafruit" |
|||
}, |
|||
"softdevice": { |
|||
"sd_flags": "-DS140", |
|||
"sd_name": "s140", |
|||
"sd_version": "6.1.1", |
|||
"sd_fwid": "0x00B6" |
|||
}, |
|||
"bootloader": { |
|||
"settings_addr": "0xFF000" |
|||
} |
|||
}, |
|||
"connectivity": [ |
|||
"bluetooth" |
|||
], |
|||
"debug": { |
|||
"jlink_device": "nRF52840_xxAA", |
|||
"onboard_tools": [ |
|||
"jlink" |
|||
], |
|||
"svd_path": "nrf52840.svd", |
|||
"openocd_target": "nrf52.cfg" |
|||
}, |
|||
"frameworks": [ |
|||
"arduino" |
|||
], |
|||
"name": "elecrow eink", |
|||
"upload": { |
|||
"maximum_ram_size": 248832, |
|||
"maximum_size": 815104, |
|||
"speed": 115200, |
|||
"use_1200bps_touch": true, |
|||
"require_upload_port": true, |
|||
"wait_for_upload_port": true, |
|||
"protocol": "nrfutil", |
|||
"protocols": [ |
|||
"jlink", |
|||
"nrfjprog", |
|||
"nrfutil", |
|||
"stlink" |
|||
] |
|||
}, |
|||
"url": "https://github.com/Elecrow-RD", |
|||
"vendor": "ELECROW" |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
{ pkgs ? import <nixpkgs> {} }: |
|||
let |
|||
in |
|||
pkgs.mkShell { |
|||
buildInputs = [ |
|||
pkgs.platformio |
|||
# optional: needed as a programmer i.e. for esp32 |
|||
pkgs.avrdude |
|||
]; |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
#ifndef NODE_PREFS_H |
|||
#define NODE_PREFS_H |
|||
|
|||
#include <cstdint> // For uint8_t, uint32_t |
|||
|
|||
#define TELEM_MODE_DENY 0 |
|||
#define TELEM_MODE_ALLOW_FLAGS 1 // use contact.flags
|
|||
#define TELEM_MODE_ALLOW_ALL 2 |
|||
|
|||
struct NodePrefs { // persisted to file
|
|||
float airtime_factor; |
|||
char node_name[32]; |
|||
float freq; |
|||
uint8_t sf; |
|||
uint8_t cr; |
|||
uint8_t reserved1; |
|||
uint8_t manual_add_contacts; |
|||
float bw; |
|||
uint8_t tx_power_dbm; |
|||
uint8_t telemetry_mode_base; |
|||
uint8_t telemetry_mode_loc; |
|||
uint8_t telemetry_mode_env; |
|||
float rx_delay_base; |
|||
uint32_t ble_pin; |
|||
}; |
|||
|
|||
#endif // NODE_PREFS_H
|
|||
@ -1,18 +1,19 @@ |
|||
#pragma once |
|||
|
|||
#include <helpers/ui/DisplayDriver.h> |
|||
#include <helpers/CommonCLI.h> |
|||
|
|||
class UITask { |
|||
DisplayDriver* _display; |
|||
unsigned long _next_read, _next_refresh, _auto_off; |
|||
int _prevBtnState; |
|||
const char* _node_name; |
|||
NodePrefs* _node_prefs; |
|||
char _version_info[32]; |
|||
|
|||
void renderCurrScreen(); |
|||
public: |
|||
UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; } |
|||
void begin(const char* node_name, const char* build_date, const char* firmware_version); |
|||
void begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version); |
|||
|
|||
void loop(); |
|||
}; |
|||
@ -1,18 +1,19 @@ |
|||
#pragma once |
|||
|
|||
#include <helpers/ui/DisplayDriver.h> |
|||
#include <helpers/CommonCLI.h> |
|||
|
|||
class UITask { |
|||
DisplayDriver* _display; |
|||
unsigned long _next_read, _next_refresh, _auto_off; |
|||
int _prevBtnState; |
|||
const char* _node_name; |
|||
NodePrefs* _node_prefs; |
|||
char _version_info[32]; |
|||
|
|||
void renderCurrScreen(); |
|||
public: |
|||
UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; } |
|||
void begin(const char* node_name, const char* build_date, const char* firmware_version); |
|||
void begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version); |
|||
|
|||
void loop(); |
|||
}; |
|||
@ -22,6 +22,7 @@ lib_deps = |
|||
rweather/Crypto @ ^0.4.0 |
|||
adafruit/RTClib @ ^2.1.3 |
|||
melopero/Melopero RV3028 @ ^1.1.0 |
|||
electroniccats/CayenneLPP @ 1.4.0 |
|||
build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1 |
|||
-D LORA_FREQ=869.525 |
|||
-D LORA_BW=250 |
|||
@ -47,6 +48,7 @@ lib_deps = |
|||
file://arch/esp32/AsyncElegantOTA |
|||
|
|||
; ----------------- NRF52 --------------------- |
|||
|
|||
[nrf52_base] |
|||
extends = arduino_base |
|||
platform = nordicnrf52 |
|||
@ -59,4 +61,26 @@ build_flags = ${nrf52_base.build_flags} |
|||
lib_deps = |
|||
${nrf52_base.lib_deps} |
|||
rweather/Crypto @ ^0.4.0 |
|||
https://github.com/adafruit/Adafruit_nRF52_Arduino |
|||
https://github.com/adafruit/Adafruit_nRF52_Arduino |
|||
|
|||
; ----------------- RP2040 --------------------- |
|||
|
|||
[rp2040_base] |
|||
extends = arduino_base |
|||
build_flags = ${arduino_base.build_flags} |
|||
-D RP2040_PLATFORM |
|||
|
|||
; ----------------- STM32 ---------------------- |
|||
|
|||
[stm32_base] |
|||
extends = arduino_base |
|||
platform = platformio/[email protected] |
|||
platform_packages = platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip |
|||
extra_scripts = post:arch/stm32/build_hex.py |
|||
build_flags = ${arduino_base.build_flags} |
|||
-D STM32_PLATFORM |
|||
-I src/helpers/stm32 |
|||
build_src_filter = ${arduino_base.build_src_filter} |
|||
+<helpers/stm32> |
|||
lib_deps = ${arduino_base.lib_deps} |
|||
file://arch/stm32/Adafruit_LittleFS_stm32 |
|||
@ -0,0 +1,17 @@ |
|||
#pragma once |
|||
|
|||
#include <RadioLib.h> |
|||
|
|||
#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
|
|||
#define SX126X_IRQ_PREAMBLE_DETECTED 0x04 |
|||
|
|||
class CustomSTM32WLx : public STM32WLx { |
|||
public: |
|||
CustomSTM32WLx(STM32WLx_Module *mod) : STM32WLx(mod) { } |
|||
|
|||
bool isReceiving() { |
|||
uint16_t irq = getIrqFlags(); |
|||
bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED); |
|||
return detected; |
|||
} |
|||
}; |
|||
@ -0,0 +1,30 @@ |
|||
#pragma once |
|||
|
|||
#include "CustomSTM32WLx.h" |
|||
#include "RadioLibWrappers.h" |
|||
#include <math.h> |
|||
|
|||
class CustomSTM32WLxWrapper : public RadioLibWrapper { |
|||
public: |
|||
CustomSTM32WLxWrapper(CustomSTM32WLx& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } |
|||
bool isReceiving() override { |
|||
if (((CustomSTM32WLx *)_radio)->isReceiving()) return true; |
|||
|
|||
idle(); // put sx126x into standby
|
|||
// do some basic CAD (blocks for ~12780 micros (on SF 10)!)
|
|||
bool activity = (((CustomSTM32WLx *)_radio)->scanChannel() == RADIOLIB_LORA_DETECTED); |
|||
if (activity) { |
|||
startRecv(); |
|||
} else { |
|||
idle(); |
|||
} |
|||
return activity; |
|||
} |
|||
float getLastRSSI() const override { return ((CustomSTM32WLx *)_radio)->getRSSI(); } |
|||
float getLastSNR() const override { return ((CustomSTM32WLx *)_radio)->getSNR(); } |
|||
|
|||
float packetScore(float snr, int packet_len) override { |
|||
int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor; |
|||
return packetScoreInt(snr, sf, packet_len); |
|||
} |
|||
}; |
|||
@ -0,0 +1,29 @@ |
|||
#pragma once |
|||
|
|||
#include <Arduino.h> |
|||
|
|||
class RefCountedDigitalPin { |
|||
uint8_t _pin; |
|||
int8_t _claims = 0; |
|||
|
|||
public: |
|||
RefCountedDigitalPin(uint8_t pin): _pin(pin) { } |
|||
|
|||
void begin() { |
|||
pinMode(_pin, OUTPUT); |
|||
digitalWrite(_pin, LOW); // initial state
|
|||
} |
|||
|
|||
void claim() { |
|||
_claims++; |
|||
if (_claims > 0) { |
|||
digitalWrite(_pin, HIGH); |
|||
} |
|||
} |
|||
void release() { |
|||
_claims--; |
|||
if (_claims == 0) { |
|||
digitalWrite(_pin, LOW); |
|||
} |
|||
} |
|||
}; |
|||
@ -0,0 +1,24 @@ |
|||
#pragma once |
|||
|
|||
#include <CayenneLPP.h> |
|||
|
|||
#define TELEM_PERM_BASE 0x01 // 'base' permission includes battery
|
|||
#define TELEM_PERM_LOCATION 0x02 |
|||
#define TELEM_PERM_ENVIRONMENT 0x04 // permission to access environment sensors
|
|||
|
|||
#define TELEM_CHANNEL_SELF 1 // LPP data channel for 'self' device
|
|||
|
|||
class SensorManager { |
|||
public: |
|||
double node_lat, node_lon; // modify these, if you want to affect Advert location
|
|||
double node_altitude; // altitude in meters
|
|||
|
|||
SensorManager() { node_lat = 0; node_lon = 0; node_altitude = 0; } |
|||
virtual bool begin() { return false; } |
|||
virtual bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { return false; } |
|||
virtual void loop() { } |
|||
virtual int getNumSettings() const { return 0; } |
|||
virtual const char* getSettingName(int i) const { return NULL; } |
|||
virtual const char* getSettingValue(int i) const { return NULL; } |
|||
virtual bool setSettingValue(const char* name, const char* value) { return false; } |
|||
}; |
|||
@ -0,0 +1,79 @@ |
|||
#pragma once |
|||
|
|||
|
|||
#include <Wire.h> |
|||
#include <Arduino.h> |
|||
#include "XPowersLib.h" |
|||
|
|||
#define XPOWERS_CHIP_AXP192 |
|||
|
|||
// LoRa radio module pins for TBeam
|
|||
#define P_LORA_DIO_1 33 // SX1262 IRQ pin
|
|||
#define P_LORA_NSS 18 |
|||
#define P_LORA_RESET 23 |
|||
#define P_LORA_BUSY 32 // SX1262 Busy pin
|
|||
#define P_LORA_SCLK 5 |
|||
#define P_LORA_MISO 19 |
|||
#define P_LORA_MOSI 27 |
|||
|
|||
#include "ESP32Board.h" |
|||
|
|||
#include <driver/rtc_io.h> |
|||
|
|||
class TBeamBoardSX1262 : public ESP32Board { |
|||
XPowersAXP192 power; |
|||
|
|||
public: |
|||
void begin() { |
|||
ESP32Board::begin(); |
|||
|
|||
power.setLDO2Voltage(3300); |
|||
power.enableLDO2(); |
|||
|
|||
power.enableBattVoltageMeasure(); |
|||
|
|||
pinMode(38, INPUT_PULLUP); |
|||
|
|||
esp_reset_reason_t reason = esp_reset_reason(); |
|||
if (reason == ESP_RST_DEEPSLEEP) { |
|||
long wakeup_source = esp_sleep_get_ext1_wakeup_status(); |
|||
if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep)
|
|||
startup_reason = BD_STARTUP_RX_PACKET; |
|||
} |
|||
|
|||
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); |
|||
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); |
|||
} |
|||
} |
|||
|
|||
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { |
|||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); |
|||
|
|||
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
|
|||
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); |
|||
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); |
|||
|
|||
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); |
|||
|
|||
if (pin_wake_btn < 0) { |
|||
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
|
|||
} else { |
|||
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
|
|||
} |
|||
|
|||
if (secs > 0) { |
|||
esp_sleep_enable_timer_wakeup(secs * 1000000); |
|||
} |
|||
|
|||
// Finally set ESP32 into sleep
|
|||
esp_deep_sleep_start(); // CPU halts here and never returns!
|
|||
} |
|||
|
|||
uint16_t getBattMilliVolts() override { |
|||
return power.getBattVoltage(); |
|||
} |
|||
|
|||
const char* getManufacturerName() const override { |
|||
return "LilyGo T-Beam SX1262"; |
|||
} |
|||
}; |
|||
@ -0,0 +1,90 @@ |
|||
#include <Arduino.h> |
|||
#include "ThinkNodeM1Board.h" |
|||
|
|||
#ifdef THINKNODE_M1 |
|||
|
|||
#include <bluefruit.h> |
|||
#include <Wire.h> |
|||
|
|||
static BLEDfu bledfu; |
|||
|
|||
static void connect_callback(uint16_t conn_handle) { |
|||
(void)conn_handle; |
|||
MESH_DEBUG_PRINTLN("BLE client connected"); |
|||
} |
|||
|
|||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { |
|||
(void)conn_handle; |
|||
(void)reason; |
|||
|
|||
MESH_DEBUG_PRINTLN("BLE client disconnected"); |
|||
} |
|||
|
|||
void ThinkNodeM1Board::begin() { |
|||
// for future use, sub-classes SHOULD call this from their begin()
|
|||
startup_reason = BD_STARTUP_NORMAL; |
|||
|
|||
Wire.begin(); |
|||
|
|||
pinMode(SX126X_POWER_EN, OUTPUT); |
|||
digitalWrite(SX126X_POWER_EN, HIGH); |
|||
delay(10); // give sx1262 some time to power up
|
|||
} |
|||
|
|||
uint16_t ThinkNodeM1Board::getBattMilliVolts() { |
|||
int adcvalue = 0; |
|||
|
|||
analogReference(AR_INTERNAL_3_0); |
|||
analogReadResolution(12); |
|||
delay(10); |
|||
|
|||
// ADC range is 0..3000mV and resolution is 12-bit (0..4095)
|
|||
adcvalue = analogRead(PIN_VBAT_READ); |
|||
// Convert the raw value to compensated mv, taking the resistor-
|
|||
// divider into account (providing the actual LIPO voltage)
|
|||
return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB); |
|||
} |
|||
|
|||
bool ThinkNodeM1Board::startOTAUpdate(const char* id, char reply[]) { |
|||
// Config the peripheral connection with maximum bandwidth
|
|||
// more SRAM required by SoftDevice
|
|||
// Note: All config***() function must be called before begin()
|
|||
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); |
|||
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); |
|||
|
|||
Bluefruit.begin(1, 0); |
|||
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
|
|||
Bluefruit.setTxPower(4); |
|||
// Set the BLE device name
|
|||
Bluefruit.setName("THINKNODE_M1_OTA"); |
|||
|
|||
Bluefruit.Periph.setConnectCallback(connect_callback); |
|||
Bluefruit.Periph.setDisconnectCallback(disconnect_callback); |
|||
|
|||
// To be consistent OTA DFU should be added first if it exists
|
|||
bledfu.begin(); |
|||
|
|||
// Set up and start advertising
|
|||
// Advertising packet
|
|||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); |
|||
Bluefruit.Advertising.addTxPower(); |
|||
Bluefruit.Advertising.addName(); |
|||
|
|||
/* Start Advertising
|
|||
- Enable auto advertising if disconnected |
|||
- Interval: fast mode = 20 ms, slow mode = 152.5 ms |
|||
- Timeout for fast mode is 30 seconds |
|||
- Start(timeout) with timeout = 0 will advertise forever (until connected) |
|||
|
|||
For recommended advertising interval |
|||
https://developer.apple.com/library/content/qa/qa1931/_index.html
|
|||
*/ |
|||
Bluefruit.Advertising.restartOnDisconnect(true); |
|||
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
|||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
|||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
|||
|
|||
strcpy(reply, "OK - started"); |
|||
return true; |
|||
} |
|||
#endif |
|||
@ -0,0 +1,49 @@ |
|||
#pragma once |
|||
|
|||
#include <MeshCore.h> |
|||
#include <Arduino.h> |
|||
|
|||
// LoRa radio module pins for Elecrow ThinkNode M1
|
|||
#define P_LORA_DIO_1 20 |
|||
#define P_LORA_NSS 24 |
|||
#define P_LORA_RESET 25 |
|||
#define P_LORA_BUSY 17 |
|||
#define P_LORA_SCLK 19 |
|||
#define P_LORA_MISO 23 |
|||
#define P_LORA_MOSI 22 |
|||
#define SX126X_POWER_EN 37 |
|||
|
|||
#define SX126X_DIO2_AS_RF_SWITCH true |
|||
#define SX126X_DIO3_TCXO_VOLTAGE 1.8 |
|||
|
|||
// built-ins
|
|||
#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096
|
|||
|
|||
#define VBAT_DIVIDER (0.5F) // 150K + 150K voltage divider on VBAT
|
|||
#define VBAT_DIVIDER_COMP (2.0F) // Compensation factor for the VBAT divider
|
|||
|
|||
#define PIN_VBAT_READ (4) |
|||
#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) |
|||
|
|||
class ThinkNodeM1Board : public mesh::MainBoard { |
|||
protected: |
|||
uint8_t startup_reason; |
|||
|
|||
public: |
|||
|
|||
void begin(); |
|||
uint16_t getBattMilliVolts() override; |
|||
bool startOTAUpdate(const char* id, char reply[]) override; |
|||
|
|||
uint8_t getStartupReason() const override { |
|||
return startup_reason; |
|||
} |
|||
|
|||
const char* getManufacturerName() const override { |
|||
return "Elecrow ThinkNode-M1"; |
|||
} |
|||
|
|||
void reboot() override { |
|||
NVIC_SystemReset(); |
|||
} |
|||
}; |
|||
@ -0,0 +1,42 @@ |
|||
#include <Arduino.h> |
|||
#include "PicoWBoard.h" |
|||
|
|||
//#include <bluefruit.h>
|
|||
#include <Wire.h> |
|||
|
|||
//static BLEDfu bledfu;
|
|||
|
|||
static void connect_callback(uint16_t conn_handle) { |
|||
(void)conn_handle; |
|||
MESH_DEBUG_PRINTLN("BLE client connected"); |
|||
} |
|||
|
|||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { |
|||
(void)conn_handle; |
|||
(void)reason; |
|||
|
|||
MESH_DEBUG_PRINTLN("BLE client disconnected"); |
|||
} |
|||
|
|||
void PicoWBoard::begin() { |
|||
// for future use, sub-classes SHOULD call this from their begin()
|
|||
startup_reason = BD_STARTUP_NORMAL; |
|||
pinMode(PIN_VBAT_READ, INPUT); |
|||
#ifdef PIN_USER_BTN |
|||
pinMode(PIN_USER_BTN, INPUT_PULLUP); |
|||
#endif |
|||
|
|||
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) |
|||
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); |
|||
#endif |
|||
|
|||
Wire.begin(); |
|||
|
|||
//pinMode(SX126X_POWER_EN, OUTPUT);
|
|||
//digitalWrite(SX126X_POWER_EN, HIGH);
|
|||
delay(10); // give sx1262 some time to power up
|
|||
} |
|||
|
|||
bool PicoWBoard::startOTAUpdate(const char* id, char reply[]) { |
|||
return false; |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
#pragma once |
|||
|
|||
#include <MeshCore.h> |
|||
#include <Arduino.h> |
|||
|
|||
// LoRa radio module pins for PicoW
|
|||
#define P_LORA_DIO_1 20 |
|||
#define P_LORA_NSS 3 |
|||
#define P_LORA_RESET 15 |
|||
#define P_LORA_BUSY 2 |
|||
#define P_LORA_SCLK 10 |
|||
#define P_LORA_MISO 12 |
|||
#define P_LORA_MOSI 11 |
|||
//#define SX126X_POWER_EN ??? // Not Sure
|
|||
|
|||
#define SX126X_DIO2_AS_RF_SWITCH true |
|||
#define SX126X_DIO3_TCXO_VOLTAGE 1.8 |
|||
|
|||
// built-ins
|
|||
#define PIN_VBAT_READ 26 |
|||
#define ADC_MULTIPLIER (3.1 * 3.3 * 1000) // MT Uses 3.1
|
|||
#define PIN_LED_BUILTIN LED_BUILTIN |
|||
|
|||
class PicoWBoard : public mesh::MainBoard { |
|||
protected: |
|||
uint8_t startup_reason; |
|||
|
|||
public: |
|||
void begin(); |
|||
uint8_t getStartupReason() const override { return startup_reason; } |
|||
|
|||
void onBeforeTransmit() override { |
|||
digitalWrite(LED_BUILTIN, HIGH); // turn TX LED on
|
|||
} |
|||
|
|||
void onAfterTransmit() override { |
|||
digitalWrite(LED_BUILTIN, LOW); // turn TX LED off
|
|||
} |
|||
|
|||
#define BATTERY_SAMPLES 8 |
|||
|
|||
uint16_t getBattMilliVolts() override { |
|||
analogReadResolution(12); |
|||
|
|||
uint32_t raw = 0; |
|||
for (int i = 0; i < BATTERY_SAMPLES; i++) { |
|||
raw += analogRead(PIN_VBAT_READ); |
|||
} |
|||
raw = raw / BATTERY_SAMPLES; |
|||
|
|||
return (ADC_MULTIPLIER * raw) / 4096; |
|||
} |
|||
|
|||
const char* getManufacturerName() const override { |
|||
return "Pico W"; |
|||
} |
|||
|
|||
void reboot() override { |
|||
//NVIC_SystemReset();
|
|||
rp2040.reboot(); |
|||
} |
|||
|
|||
bool startOTAUpdate(const char* id, char reply[]) override; |
|||
}; |
|||
@ -0,0 +1,231 @@ |
|||
#include "EnvironmentSensorManager.h" |
|||
|
|||
#if ENV_INCLUDE_AHTX0 |
|||
#define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address
|
|||
#include <Adafruit_AHTX0.h> |
|||
static Adafruit_AHTX0 AHTX0; |
|||
#endif |
|||
|
|||
#if ENV_INCLUDE_BME280 |
|||
#define TELEM_BME280_ADDRESS 0x76 // BME280 environmental sensor I2C address
|
|||
#define TELEM_BME280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level
|
|||
#include <Adafruit_BME280.h> |
|||
static Adafruit_BME280 BME280; |
|||
#endif |
|||
|
|||
#if ENV_INCLUDE_INA3221 |
|||
#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current sensor I2C address
|
|||
#define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts
|
|||
#define TELEM_INA3221_NUM_CHANNELS 3 |
|||
#include <Adafruit_INA3221.h> |
|||
static Adafruit_INA3221 INA3221; |
|||
#endif |
|||
|
|||
#if ENV_INCLUDE_INA219 |
|||
#define TELEM_INA219_ADDRESS 0x40 // INA219 single channel current sensor I2C address
|
|||
#include <Adafruit_INA219.h> |
|||
static Adafruit_INA219 INA219(TELEM_INA219_ADDRESS); |
|||
#endif |
|||
|
|||
bool EnvironmentSensorManager::begin() { |
|||
#if ENV_INCLUDE_GPS |
|||
initBasicGPS(); |
|||
#endif |
|||
|
|||
#if ENV_INCLUDE_AHTX0 |
|||
if (AHTX0.begin(&Wire, 0, TELEM_AHTX_ADDRESS)) { |
|||
MESH_DEBUG_PRINTLN("Found AHT10/AHT20 at address: %02X", TELEM_AHTX_ADDRESS); |
|||
AHTX0_initialized = true; |
|||
} else { |
|||
AHTX0_initialized = false; |
|||
MESH_DEBUG_PRINTLN("AHT10/AHT20 was not found at I2C address %02X", TELEM_AHTX_ADDRESS); |
|||
} |
|||
#endif |
|||
|
|||
#if ENV_INCLUDE_BME280 |
|||
if (BME280.begin(TELEM_BME280_ADDRESS, &Wire)) { |
|||
MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS); |
|||
MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID()); |
|||
BME280_initialized = true; |
|||
} else { |
|||
BME280_initialized = false; |
|||
MESH_DEBUG_PRINTLN("BME280 was not found at I2C address %02X", TELEM_BME280_ADDRESS); |
|||
} |
|||
#endif |
|||
|
|||
#if ENV_INCLUDE_INA3221 |
|||
if (INA3221.begin(TELEM_INA3221_ADDRESS, &Wire)) { |
|||
MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", TELEM_INA3221_ADDRESS); |
|||
MESH_DEBUG_PRINTLN("%04X %04X", INA3221.getDieID(), INA3221.getManufacturerID()); |
|||
|
|||
for(int i = 0; i < 3; i++) { |
|||
INA3221.setShuntResistance(i, TELEM_INA3221_SHUNT_VALUE); |
|||
} |
|||
INA3221_initialized = true; |
|||
} else { |
|||
INA3221_initialized = false; |
|||
MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS); |
|||
} |
|||
#endif |
|||
|
|||
#if ENV_INCLUDE_INA219 |
|||
if (INA219.begin(&Wire)) { |
|||
MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", TELEM_INA219_ADDRESS); |
|||
INA219_initialized = true; |
|||
} else { |
|||
INA219_initialized = false; |
|||
MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS); |
|||
} |
|||
#endif |
|||
|
|||
return true; |
|||
} |
|||
|
|||
bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { |
|||
next_available_channel = TELEM_CHANNEL_SELF + 1; |
|||
|
|||
if (requester_permissions & TELEM_PERM_LOCATION) { |
|||
telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, 0.0f); // allow lat/lon via telemetry even if no GPS is detected
|
|||
} |
|||
|
|||
if (requester_permissions & TELEM_PERM_ENVIRONMENT) { |
|||
|
|||
#if ENV_INCLUDE_AHTX0 |
|||
if (AHTX0_initialized) { |
|||
sensors_event_t humidity, temp; |
|||
AHTX0.getEvent(&humidity, &temp); |
|||
telemetry.addTemperature(TELEM_CHANNEL_SELF, temp.temperature); |
|||
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, humidity.relative_humidity); |
|||
} |
|||
#endif |
|||
|
|||
#if ENV_INCLUDE_BME280 |
|||
if (BME280_initialized) { |
|||
telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature()); |
|||
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME280.readHumidity()); |
|||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280.readPressure()); |
|||
telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA)); |
|||
} |
|||
#endif |
|||
|
|||
#if ENV_INCLUDE_INA3221 |
|||
if (INA3221_initialized) { |
|||
for(int i = 0; i < TELEM_INA3221_NUM_CHANNELS; i++) { |
|||
// add only enabled INA3221 channels to telemetry
|
|||
if (INA3221.isChannelEnabled(i)) { |
|||
float voltage = INA3221.getBusVoltage(i); |
|||
float current = INA3221.getCurrentAmps(i); |
|||
telemetry.addVoltage(next_available_channel, voltage); |
|||
telemetry.addCurrent(next_available_channel, current); |
|||
telemetry.addPower(next_available_channel, voltage * current); |
|||
next_available_channel++; |
|||
} |
|||
} |
|||
} |
|||
#endif |
|||
|
|||
#if ENV_INCLUDE_INA219 |
|||
if (INA219_initialized) { |
|||
telemetry.addVoltage(next_available_channel, INA219.getBusVoltage_V()); |
|||
telemetry.addCurrent(next_available_channel, INA219.getCurrent_mA() / 1000); |
|||
telemetry.addPower(next_available_channel, INA219.getPower_mW() / 1000); |
|||
next_available_channel++; |
|||
} |
|||
#endif |
|||
|
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
|
|||
int EnvironmentSensorManager::getNumSettings() const { |
|||
#if ENV_INCLUDE_GPS |
|||
return gps_detected ? 1 : 0; // only show GPS setting if GPS is detected
|
|||
#else |
|||
return 0; |
|||
#endif |
|||
} |
|||
|
|||
const char* EnvironmentSensorManager::getSettingName(int i) const { |
|||
#if ENV_INCLUDE_GPS |
|||
return (gps_detected && i == 0) ? "gps" : NULL; |
|||
#else |
|||
return NULL; |
|||
#endif |
|||
} |
|||
|
|||
const char* EnvironmentSensorManager::getSettingValue(int i) const { |
|||
#if ENV_INCLUDE_GPS |
|||
if (gps_detected && i == 0) { |
|||
return gps_active ? "1" : "0"; |
|||
} |
|||
#endif |
|||
return NULL; |
|||
} |
|||
|
|||
bool EnvironmentSensorManager::setSettingValue(const char* name, const char* value) { |
|||
#if ENV_INCLUDE_GPS |
|||
if (gps_detected && strcmp(name, "gps") == 0) { |
|||
if (strcmp(value, "0") == 0) { |
|||
stop_gps(); |
|||
} else { |
|||
start_gps(); |
|||
} |
|||
return true; |
|||
} |
|||
#endif |
|||
return false; // not supported
|
|||
} |
|||
|
|||
#if ENV_INCLUDE_GPS |
|||
void EnvironmentSensorManager::initBasicGPS() { |
|||
Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); |
|||
Serial1.begin(9600); |
|||
|
|||
// Try to detect if GPS is physically connected to determine if we should expose the setting
|
|||
pinMode(PIN_GPS_EN, OUTPUT); |
|||
digitalWrite(PIN_GPS_EN, HIGH); // Power on GPS
|
|||
|
|||
// Give GPS a moment to power up and send data
|
|||
delay(1000); |
|||
|
|||
// We'll consider GPS detected if we see any data on Serial1
|
|||
gps_detected = (Serial1.available() > 0); |
|||
|
|||
if (gps_detected) { |
|||
MESH_DEBUG_PRINTLN("GPS detected"); |
|||
digitalWrite(PIN_GPS_EN, LOW); // Power off GPS until the setting is changed
|
|||
} else { |
|||
MESH_DEBUG_PRINTLN("No GPS detected"); |
|||
digitalWrite(PIN_GPS_EN, LOW); |
|||
} |
|||
} |
|||
|
|||
void EnvironmentSensorManager::start_gps() { |
|||
gps_active = true; |
|||
pinMode(PIN_GPS_EN, OUTPUT); |
|||
digitalWrite(PIN_GPS_EN, HIGH); |
|||
} |
|||
|
|||
void EnvironmentSensorManager::stop_gps() { |
|||
gps_active = false; |
|||
pinMode(PIN_GPS_EN, OUTPUT); |
|||
digitalWrite(PIN_GPS_EN, LOW); |
|||
} |
|||
|
|||
void EnvironmentSensorManager::loop() { |
|||
static long next_gps_update = 0; |
|||
|
|||
_location->loop(); |
|||
|
|||
if (millis() > next_gps_update) { |
|||
if (_location->isValid()) { |
|||
node_lat = ((double)_location->getLatitude())/1000000.; |
|||
node_lon = ((double)_location->getLongitude())/1000000.; |
|||
MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); |
|||
} |
|||
next_gps_update = millis() + 1000; |
|||
} |
|||
} |
|||
#endif |
|||
@ -0,0 +1,42 @@ |
|||
#pragma once |
|||
|
|||
#include <Mesh.h> |
|||
#include <helpers/SensorManager.h> |
|||
#include <helpers/sensors/LocationProvider.h> |
|||
|
|||
class EnvironmentSensorManager : public SensorManager { |
|||
protected: |
|||
int next_available_channel = TELEM_CHANNEL_SELF + 1; |
|||
|
|||
bool AHTX0_initialized = false; |
|||
bool BME280_initialized = false; |
|||
bool INA3221_initialized = false; |
|||
bool INA219_initialized = false; |
|||
|
|||
bool gps_detected = false; |
|||
bool gps_active = false; |
|||
|
|||
#if ENV_INCLUDE_GPS |
|||
LocationProvider* _location; |
|||
void start_gps(); |
|||
void stop_gps(); |
|||
void initBasicGPS(); |
|||
#endif |
|||
|
|||
|
|||
public: |
|||
#if ENV_INCLUDE_GPS |
|||
EnvironmentSensorManager(LocationProvider &location): _location(&location){}; |
|||
#else |
|||
EnvironmentSensorManager(){}; |
|||
#endif |
|||
bool begin() override; |
|||
bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; |
|||
#if ENV_INCLUDE_GPS |
|||
void loop() override; |
|||
#endif |
|||
int getNumSettings() const override; |
|||
const char* getSettingName(int i) const override; |
|||
const char* getSettingValue(int i) const override; |
|||
bool setSettingValue(const char* name, const char* value) override; |
|||
}; |
|||
@ -0,0 +1,18 @@ |
|||
#pragma once |
|||
|
|||
#include "Mesh.h" |
|||
|
|||
|
|||
class LocationProvider { |
|||
|
|||
public: |
|||
virtual long getLatitude() = 0; |
|||
virtual long getLongitude() = 0; |
|||
virtual long getAltitude() = 0; |
|||
virtual bool isValid() = 0; |
|||
virtual long getTimestamp() = 0; |
|||
virtual void reset(); |
|||
virtual void begin(); |
|||
virtual void stop(); |
|||
virtual void loop(); |
|||
}; |
|||
@ -0,0 +1,85 @@ |
|||
#pragma once |
|||
|
|||
#include "LocationProvider.h" |
|||
#include <MicroNMEA.h> |
|||
#include <RTClib.h> |
|||
|
|||
#ifndef GPS_EN |
|||
#define GPS_EN (-1) |
|||
#endif |
|||
|
|||
#ifndef GPS_RESET |
|||
#define GPS_RESET (-1) |
|||
#endif |
|||
|
|||
#ifndef GPS_RESET_FORCE |
|||
#define GPS_RESET_FORCE LOW |
|||
#endif |
|||
|
|||
class MicroNMEALocationProvider : public LocationProvider { |
|||
char _nmeaBuffer[100]; |
|||
MicroNMEA nmea; |
|||
Stream* _gps_serial; |
|||
int _pin_reset; |
|||
int _pin_en; |
|||
|
|||
public : |
|||
MicroNMEALocationProvider(Stream& ser, int pin_reset = GPS_RESET, int pin_en = GPS_EN) : |
|||
_gps_serial(&ser), nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _pin_reset(pin_reset), _pin_en(pin_en) { |
|||
if (_pin_reset != -1) { |
|||
pinMode(_pin_reset, OUTPUT); |
|||
digitalWrite(_pin_reset, GPS_RESET_FORCE); |
|||
} |
|||
if (_pin_en != -1) { |
|||
pinMode(_pin_en, OUTPUT); |
|||
digitalWrite(_pin_en, LOW); |
|||
} |
|||
} |
|||
|
|||
void begin() override { |
|||
if (_pin_reset != -1) { |
|||
digitalWrite(_pin_reset, !GPS_RESET_FORCE); |
|||
} |
|||
if (_pin_en != -1) { |
|||
digitalWrite(_pin_en, HIGH); |
|||
} |
|||
} |
|||
|
|||
void reset() override { |
|||
if (_pin_reset != -1) { |
|||
digitalWrite(_pin_reset, GPS_RESET_FORCE); |
|||
delay(100); |
|||
digitalWrite(_pin_reset, !GPS_RESET_FORCE); |
|||
} |
|||
} |
|||
|
|||
void stop() override { |
|||
if (_pin_en != -1) { |
|||
digitalWrite(_pin_en, LOW); |
|||
} |
|||
} |
|||
|
|||
long getLatitude() override { return nmea.getLatitude(); } |
|||
long getLongitude() override { return nmea.getLongitude(); } |
|||
long getAltitude() override { |
|||
long alt = 0; |
|||
nmea.getAltitude(alt); |
|||
return alt; |
|||
} |
|||
bool isValid() override { return nmea.isValid(); } |
|||
|
|||
long getTimestamp() override { |
|||
DateTime dt(nmea.getYear(), nmea.getMonth(),nmea.getDay(),nmea.getHour(),nmea.getMinute(),nmea.getSecond()); |
|||
return dt.unixtime(); |
|||
} |
|||
|
|||
void loop() override { |
|||
while (_gps_serial->available()) { |
|||
char c = _gps_serial->read(); |
|||
#ifdef GPS_NMEA_DEBUG |
|||
Serial.print(c); |
|||
#endif |
|||
nmea.process(c); |
|||
} |
|||
} |
|||
}; |
|||
@ -0,0 +1,142 @@ |
|||
/*
|
|||
* The MIT License (MIT) |
|||
* |
|||
* Copyright (c) 2019 hathach for Adafruit Industries |
|||
* |
|||
* Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
* of this software and associated documentation files (the "Software"), to deal |
|||
* in the Software without restriction, including without limitation the rights |
|||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
* copies of the Software, and to permit persons to whom the Software is |
|||
* furnished to do so, subject to the following conditions: |
|||
* |
|||
* The above copyright notice and this permission notice shall be included in |
|||
* all copies or substantial portions of the Software. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|||
* THE SOFTWARE. |
|||
*/ |
|||
|
|||
#include <Arduino.h> |
|||
#include "InternalFileSystem.h" |
|||
|
|||
//--------------------------------------------------------------------+
|
|||
// LFS Disk IO
|
|||
//--------------------------------------------------------------------+
|
|||
static int _internal_flash_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) |
|||
{ |
|||
if (!buffer || !size) return LFS_ERR_INVAL; |
|||
lfs_block_t address = LFS_FLASH_ADDR_BASE + (block * FLASH_PAGE_SIZE + off); |
|||
memcpy(buffer, (void *)address, size); |
|||
return LFS_ERR_OK; |
|||
} |
|||
|
|||
// Program a region in a block. The block must have previously
|
|||
// been erased. Negative error codes are propogated to the user.
|
|||
// May return LFS_ERR_CORRUPT if the block should be considered bad.
|
|||
static int _internal_flash_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) |
|||
{ |
|||
HAL_StatusTypeDef hal_rc = HAL_OK; |
|||
lfs_block_t addr = LFS_FLASH_ADDR_BASE + (block * FLASH_PAGE_SIZE + off); |
|||
uint64_t *bufp = (uint64_t *) buffer; |
|||
|
|||
if (HAL_FLASH_Unlock() != HAL_OK) return LFS_ERR_IO; |
|||
for (uint32_t i = 0; i < size/8; i++) { |
|||
if ((addr < LFS_FLASH_ADDR_BASE) || (addr > FLASH_END_ADDR)) { |
|||
HAL_FLASH_Lock(); |
|||
return LFS_ERR_INVAL; |
|||
} |
|||
hal_rc = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr, *bufp); |
|||
addr += 8; |
|||
bufp += 1; |
|||
} |
|||
if (HAL_FLASH_Lock() != HAL_OK) return LFS_ERR_IO; |
|||
|
|||
return hal_rc == HAL_OK ? LFS_ERR_OK : LFS_ERR_IO; |
|||
} |
|||
|
|||
// Erase a block. A block must be erased before being programmed.
|
|||
// The state of an erased block is undefined. Negative error codes
|
|||
// are propogated to the user.
|
|||
// May return LFS_ERR_CORRUPT if the block should be considered bad.
|
|||
static int _internal_flash_erase(const struct lfs_config *c, lfs_block_t block) |
|||
{ |
|||
HAL_StatusTypeDef hal_rc; |
|||
lfs_block_t address = LFS_FLASH_ADDR_BASE + (block * FLASH_PAGE_SIZE); |
|||
uint32_t pageError = 0; |
|||
FLASH_EraseInitTypeDef EraseInitStruct = { |
|||
.TypeErase = FLASH_TYPEERASE_PAGES, |
|||
.Page = 0, |
|||
.NbPages = 1 |
|||
}; |
|||
|
|||
if ((address < LFS_FLASH_ADDR_BASE) || (address > FLASH_END_ADDR)) { |
|||
return LFS_ERR_INVAL; |
|||
} |
|||
EraseInitStruct.Page = (address - FLASH_BASE) / FLASH_PAGE_SIZE; |
|||
HAL_FLASH_Unlock(); |
|||
hal_rc = HAL_FLASHEx_Erase(&EraseInitStruct, &pageError); |
|||
HAL_FLASH_Lock(); |
|||
|
|||
return hal_rc == HAL_OK ? LFS_ERR_OK : LFS_ERR_IO; |
|||
} |
|||
|
|||
// Sync the state of the underlying block device. Negative error codes
|
|||
// are propogated to the user.
|
|||
static int _internal_flash_sync(const struct lfs_config *c) |
|||
{ |
|||
return LFS_ERR_OK; // don't need sync
|
|||
} |
|||
|
|||
struct lfs_config _InternalFSConfig = { |
|||
.context = NULL, |
|||
.read = _internal_flash_read, |
|||
.prog = _internal_flash_prog, |
|||
.erase = _internal_flash_erase, |
|||
.sync = _internal_flash_sync, |
|||
|
|||
.read_size = LFS_BLOCK_SIZE, |
|||
.prog_size = LFS_BLOCK_SIZE, |
|||
.block_size = LFS_BLOCK_SIZE, |
|||
.block_count = LFS_FLASH_TOTAL_SIZE / LFS_BLOCK_SIZE, |
|||
.lookahead = 128, |
|||
|
|||
.read_buffer = NULL, |
|||
.prog_buffer = NULL, |
|||
.lookahead_buffer = NULL, |
|||
.file_buffer = NULL |
|||
}; |
|||
|
|||
InternalFileSystem InternalFS; |
|||
|
|||
//--------------------------------------------------------------------+
|
|||
//
|
|||
//--------------------------------------------------------------------+
|
|||
|
|||
InternalFileSystem::InternalFileSystem(void) |
|||
: Adafruit_LittleFS(&_InternalFSConfig) |
|||
{ |
|||
|
|||
} |
|||
|
|||
bool InternalFileSystem::begin(void) |
|||
{ |
|||
#ifdef FORMAT_FS |
|||
this->format(); |
|||
#endif |
|||
// failed to mount, erase all sector then format and mount again
|
|||
if ( !Adafruit_LittleFS::begin() ) |
|||
{ |
|||
// lfs format
|
|||
this->format(); |
|||
// mount again if still failed, give up
|
|||
if ( !Adafruit_LittleFS::begin() ) return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
/*
|
|||
* The MIT License (MIT) |
|||
* |
|||
* Copyright (c) 2019 hathach for Adafruit Industries |
|||
* |
|||
* Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
* of this software and associated documentation files (the "Software"), to deal |
|||
* in the Software without restriction, including without limitation the rights |
|||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
* copies of the Software, and to permit persons to whom the Software is |
|||
* furnished to do so, subject to the following conditions: |
|||
* |
|||
* The above copyright notice and this permission notice shall be included in |
|||
* all copies or substantial portions of the Software. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|||
* THE SOFTWARE. |
|||
*/ |
|||
|
|||
#ifndef INTERNALFILESYSTEM_H_ |
|||
#define INTERNALFILESYSTEM_H_ |
|||
|
|||
#include "Adafruit_LittleFS.h" |
|||
|
|||
#ifndef LFS_FLASH_TOTAL_SIZE /* Flash size can be configured in platformio.ini */ |
|||
#define LFS_FLASH_TOTAL_SIZE (16 * 2048) /* defaults to 32k flash */ |
|||
#endif |
|||
#define LFS_BLOCK_SIZE (2048) |
|||
#define LFS_FLASH_ADDR_BASE (FLASH_END_ADDR - LFS_FLASH_TOTAL_SIZE + 1) |
|||
|
|||
class InternalFileSystem : public Adafruit_LittleFS |
|||
{ |
|||
public: |
|||
InternalFileSystem(void); |
|||
|
|||
// overwrite to also perform low level format (sector erase of whole flash region)
|
|||
bool begin(void); |
|||
}; |
|||
|
|||
extern InternalFileSystem InternalFS; |
|||
|
|||
#endif /* INTERNALFILESYSTEM_H_ */ |
|||
|
|||
@ -0,0 +1,29 @@ |
|||
#pragma once |
|||
|
|||
#include <MeshCore.h> |
|||
#include <Arduino.h> |
|||
|
|||
class STM32Board : public mesh::MainBoard { |
|||
protected: |
|||
uint8_t startup_reason; |
|||
|
|||
public: |
|||
void begin() { |
|||
startup_reason = BD_STARTUP_NORMAL; |
|||
} |
|||
|
|||
uint8_t getStartupReason() const override { return startup_reason; } |
|||
|
|||
uint16_t getBattMilliVolts() override { |
|||
return 0; // not supported
|
|||
} |
|||
|
|||
const char* getManufacturerName() const override { |
|||
return "Generic STM32"; |
|||
} |
|||
|
|||
void reboot() override { |
|||
} |
|||
|
|||
bool startOTAUpdate(const char* id, char reply[]) override { return false; }; |
|||
}; |
|||
File diff suppressed because it is too large
@ -0,0 +1,395 @@ |
|||
/**
|
|||
* The MIT License (MIT) |
|||
* |
|||
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn |
|||
* Copyright (c) 2018 by Fabrice Weinberg |
|||
* Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de |
|||
* |
|||
* Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
* of this software and associated documentation files (the "Software"), to deal |
|||
* in the Software without restriction, including without limitation the rights |
|||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
* copies of the Software, and to permit persons to whom the Software is |
|||
* furnished to do so, subject to the following conditions: |
|||
* |
|||
* The above copyright notice and this permission notice shall be included in all |
|||
* copies or substantial portions of the Software. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
* SOFTWARE. |
|||
* |
|||
* ThingPulse invests considerable time and money to develop these open source libraries. |
|||
* Please support us by buying our products (and not the clones) from |
|||
* https://thingpulse.com
|
|||
* |
|||
*/ |
|||
|
|||
#ifndef OLEDDISPLAY_h |
|||
#define OLEDDISPLAY_h |
|||
|
|||
#include <cstdarg> |
|||
|
|||
#ifdef ARDUINO |
|||
#include <Arduino.h> |
|||
#elif __MBED__ |
|||
#define pgm_read_byte(addr) (*(const unsigned char *)(addr)) |
|||
|
|||
#include <mbed.h> |
|||
#define delay(x) wait_ms(x) |
|||
#define yield() void() |
|||
|
|||
/*
|
|||
* This is a little Arduino String emulation to keep the OLEDDisplay |
|||
* library code in common between Arduino and mbed-os |
|||
*/ |
|||
class String { |
|||
public: |
|||
String(const char *s) { _str = s; }; |
|||
int length() { return strlen(_str); }; |
|||
const char *c_str() { return _str; }; |
|||
void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const { |
|||
memcpy(buf, _str + index, std::min(bufsize, strlen(_str))); |
|||
}; |
|||
private: |
|||
const char *_str; |
|||
}; |
|||
|
|||
#else |
|||
#error "Unkown operating system" |
|||
#endif |
|||
|
|||
#include "OLEDDisplayFonts.h" |
|||
|
|||
//#define DEBUG_OLEDDISPLAY(...) Serial.printf( __VA_ARGS__ )
|
|||
//#define DEBUG_OLEDDISPLAY(...) dprintf("%s", __VA_ARGS__ )
|
|||
|
|||
#ifndef DEBUG_OLEDDISPLAY |
|||
#define DEBUG_OLEDDISPLAY(...) |
|||
#endif |
|||
|
|||
// Use DOUBLE BUFFERING by default
|
|||
#ifndef OLEDDISPLAY_REDUCE_MEMORY |
|||
#define OLEDDISPLAY_DOUBLE_BUFFER |
|||
#endif |
|||
|
|||
// Header Values
|
|||
#define JUMPTABLE_BYTES 4 |
|||
|
|||
#define JUMPTABLE_LSB 1 |
|||
#define JUMPTABLE_SIZE 2 |
|||
#define JUMPTABLE_WIDTH 3 |
|||
#define JUMPTABLE_START 4 |
|||
|
|||
#define WIDTH_POS 0 |
|||
#define HEIGHT_POS 1 |
|||
#define FIRST_CHAR_POS 2 |
|||
#define CHAR_NUM_POS 3 |
|||
|
|||
|
|||
// Display commands
|
|||
#define CHARGEPUMP 0x8D |
|||
#define COLUMNADDR 0x21 |
|||
#define COMSCANDEC 0xC8 |
|||
#define COMSCANINC 0xC0 |
|||
#define DISPLAYALLON 0xA5 |
|||
#define DISPLAYALLON_RESUME 0xA4 |
|||
#define DISPLAYOFF 0xAE |
|||
#define DISPLAYON 0xAF |
|||
#define EXTERNALVCC 0x1 |
|||
#define INVERTDISPLAY 0xA7 |
|||
#define MEMORYMODE 0x20 |
|||
#define NORMALDISPLAY 0xA6 |
|||
#define PAGEADDR 0x22 |
|||
#define SEGREMAP 0xA0 |
|||
#define SETCOMPINS 0xDA |
|||
#define SETCONTRAST 0x81 |
|||
#define SETDISPLAYCLOCKDIV 0xD5 |
|||
#define SETDISPLAYOFFSET 0xD3 |
|||
#define SETHIGHCOLUMN 0x10 |
|||
#define SETLOWCOLUMN 0x00 |
|||
#define SETMULTIPLEX 0xA8 |
|||
#define SETPRECHARGE 0xD9 |
|||
#define SETSEGMENTREMAP 0xA1 |
|||
#define SETSTARTLINE 0x40 |
|||
#define SETVCOMDETECT 0xDB |
|||
#define SWITCHCAPVCC 0x2 |
|||
|
|||
#ifndef _swap_int16_t |
|||
#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; } |
|||
#endif |
|||
|
|||
enum OLEDDISPLAY_COLOR { |
|||
BLACK = 0, |
|||
WHITE = 1, |
|||
INVERSE = 2 |
|||
}; |
|||
|
|||
enum OLEDDISPLAY_TEXT_ALIGNMENT { |
|||
TEXT_ALIGN_LEFT = 0, |
|||
TEXT_ALIGN_RIGHT = 1, |
|||
TEXT_ALIGN_CENTER = 2, |
|||
TEXT_ALIGN_CENTER_BOTH = 3 |
|||
}; |
|||
|
|||
|
|||
enum OLEDDISPLAY_GEOMETRY { |
|||
GEOMETRY_128_64 = 0, |
|||
GEOMETRY_128_32 = 1, |
|||
GEOMETRY_64_48 = 2, |
|||
GEOMETRY_64_32 = 3, |
|||
GEOMETRY_RAWMODE = 4, |
|||
GEOMETRY_128_128 = 5 |
|||
}; |
|||
|
|||
enum HW_I2C { |
|||
I2C_ONE, |
|||
I2C_TWO |
|||
}; |
|||
|
|||
typedef char (*FontTableLookupFunction)(const uint8_t ch); |
|||
char DefaultFontTableLookup(const uint8_t ch); |
|||
|
|||
|
|||
#ifdef ARDUINO |
|||
class OLEDDisplay : public Print { |
|||
#elif __MBED__ |
|||
class OLEDDisplay : public Stream { |
|||
#else |
|||
#error "Unkown operating system" |
|||
#endif |
|||
|
|||
public: |
|||
OLEDDisplay(); |
|||
virtual ~OLEDDisplay(); |
|||
|
|||
uint16_t width(void) const { return displayWidth; }; |
|||
uint16_t height(void) const { return displayHeight; }; |
|||
|
|||
// Use this to resume after a deep sleep without resetting the display (what init() would do).
|
|||
// Returns true if connection to the display was established and the buffer allocated, false otherwise.
|
|||
bool allocateBuffer(); |
|||
|
|||
// Allocates the buffer and initializes the driver & display. Resets the display!
|
|||
// Returns false if buffer allocation failed, true otherwise.
|
|||
bool init(); |
|||
|
|||
// Free the memory used by the display
|
|||
void end(); |
|||
|
|||
// Cycle through the initialization
|
|||
void resetDisplay(void); |
|||
|
|||
/* Drawing functions */ |
|||
// Sets the color of all pixel operations
|
|||
void setColor(OLEDDISPLAY_COLOR color); |
|||
|
|||
// Returns the current color.
|
|||
OLEDDISPLAY_COLOR getColor(); |
|||
|
|||
// Draw a pixel at given position
|
|||
void setPixel(int16_t x, int16_t y); |
|||
|
|||
// Draw a pixel at given position and color
|
|||
void setPixelColor(int16_t x, int16_t y, OLEDDISPLAY_COLOR color); |
|||
|
|||
// Clear a pixel at given position FIXME: INVERSE is untested with this function
|
|||
void clearPixel(int16_t x, int16_t y); |
|||
|
|||
// Draw a line from position 0 to position 1
|
|||
void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1); |
|||
|
|||
// Draw the border of a rectangle at the given location
|
|||
void drawRect(int16_t x, int16_t y, int16_t width, int16_t height); |
|||
|
|||
// Fill the rectangle
|
|||
void fillRect(int16_t x, int16_t y, int16_t width, int16_t height); |
|||
|
|||
// Draw the border of a circle
|
|||
void drawCircle(int16_t x, int16_t y, int16_t radius); |
|||
|
|||
// Draw all Quadrants specified in the quads bit mask
|
|||
void drawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads); |
|||
|
|||
// Fill circle
|
|||
void fillCircle(int16_t x, int16_t y, int16_t radius); |
|||
|
|||
// Draw an empty triangle i.e. only the outline
|
|||
void drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2); |
|||
|
|||
// Draw a solid triangle i.e. filled
|
|||
void fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2); |
|||
|
|||
// Draw a line horizontally
|
|||
void drawHorizontalLine(int16_t x, int16_t y, int16_t length); |
|||
|
|||
// Draw a line vertically
|
|||
void drawVerticalLine(int16_t x, int16_t y, int16_t length); |
|||
|
|||
// Draws a rounded progress bar with the outer dimensions given by width and height. Progress is
|
|||
// a unsigned byte value between 0 and 100
|
|||
void drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress); |
|||
|
|||
// Draw a bitmap in the internal image format
|
|||
void drawFastImage(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *image); |
|||
|
|||
// Draw a XBM
|
|||
void drawXbm(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *xbm); |
|||
|
|||
// Draw icon 16x16 xbm format
|
|||
void drawIco16x16(int16_t x, int16_t y, const uint8_t *ico, bool inverse = false); |
|||
|
|||
/* Text functions */ |
|||
|
|||
// Draws a string at the given location, returns how many chars have been written
|
|||
uint16_t drawString(int16_t x, int16_t y, const String &text); |
|||
|
|||
// Draws a formatted string (like printf) at the given location
|
|||
void drawStringf(int16_t x, int16_t y, char* buffer, String format, ... ); |
|||
|
|||
// Draws a String with a maximum width at the given location.
|
|||
// If the given String is wider than the specified width
|
|||
// The text will be wrapped to the next line at a space or dash
|
|||
// returns 0 if everything fits on the screen or the numbers of characters in the
|
|||
// first line if not
|
|||
uint16_t drawStringMaxWidth(int16_t x, int16_t y, uint16_t maxLineWidth, const String &text); |
|||
|
|||
// Returns the width of the const char* with the current
|
|||
// font settings
|
|||
uint16_t getStringWidth(const char* text, uint16_t length, bool utf8 = false); |
|||
|
|||
// Convencience method for the const char version
|
|||
uint16_t getStringWidth(const String &text); |
|||
|
|||
// Specifies relative to which anchor point
|
|||
// the text is rendered. Available constants:
|
|||
// TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER_BOTH
|
|||
void setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment); |
|||
|
|||
// Sets the current font. Available default fonts
|
|||
// ArialMT_Plain_10, ArialMT_Plain_16, ArialMT_Plain_24
|
|||
void setFont(const uint8_t *fontData); |
|||
|
|||
// Set the function that will convert utf-8 to font table index
|
|||
void setFontTableLookupFunction(FontTableLookupFunction function); |
|||
|
|||
/* Display functions */ |
|||
|
|||
// Turn the display on
|
|||
void displayOn(void); |
|||
|
|||
// Turn the display offs
|
|||
void displayOff(void); |
|||
|
|||
// Inverted display mode
|
|||
void invertDisplay(void); |
|||
|
|||
// Normal display mode
|
|||
void normalDisplay(void); |
|||
|
|||
// Set display contrast
|
|||
// really low brightness & contrast: contrast = 10, precharge = 5, comdetect = 0
|
|||
// normal brightness & contrast: contrast = 100
|
|||
void setContrast(uint8_t contrast, uint8_t precharge = 241, uint8_t comdetect = 64); |
|||
|
|||
// Convenience method to access
|
|||
virtual void setBrightness(uint8_t); |
|||
|
|||
// Reset display rotation or mirroring
|
|||
void resetOrientation(); |
|||
|
|||
// Turn the display upside down
|
|||
void flipScreenVertically(); |
|||
|
|||
// Mirror the display (to be used in a mirror or as a projector)
|
|||
void mirrorScreen(); |
|||
|
|||
// Write the buffer to the display memory
|
|||
virtual void display(void) = 0; |
|||
|
|||
// Clear the local pixel buffer
|
|||
void clear(void); |
|||
|
|||
// Log buffer implementation
|
|||
|
|||
// This will define the lines and characters you can
|
|||
// print to the screen. When you exeed the buffer size (lines * chars)
|
|||
// the output may be truncated due to the size constraint.
|
|||
bool setLogBuffer(uint16_t lines, uint16_t chars); |
|||
|
|||
// Draw the log buffer at position (x, y)
|
|||
void drawLogBuffer(uint16_t x, uint16_t y); |
|||
|
|||
// Get screen geometry
|
|||
uint16_t getWidth(void); |
|||
uint16_t getHeight(void); |
|||
|
|||
// Implement needed function to be compatible with Print class
|
|||
size_t write(uint8_t c); |
|||
size_t write(const char* s); |
|||
|
|||
// Implement needed function to be compatible with Stream class
|
|||
#ifdef __MBED__ |
|||
int _putc(int c); |
|||
int _getc() { return -1; }; |
|||
#endif |
|||
|
|||
|
|||
uint8_t *buffer; |
|||
|
|||
#ifdef OLEDDISPLAY_DOUBLE_BUFFER |
|||
uint8_t *buffer_back; |
|||
#endif |
|||
|
|||
// Set the correct height, width and buffer for the geometry
|
|||
void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width = 0, uint16_t height = 0); |
|||
|
|||
protected: |
|||
|
|||
OLEDDISPLAY_GEOMETRY geometry; |
|||
|
|||
uint16_t displayWidth; |
|||
uint16_t displayHeight; |
|||
uint16_t displayBufferSize; |
|||
|
|||
OLEDDISPLAY_TEXT_ALIGNMENT textAlignment; |
|||
OLEDDISPLAY_COLOR color; |
|||
|
|||
const uint8_t *fontData; |
|||
|
|||
// State values for logBuffer
|
|||
uint16_t logBufferSize; |
|||
uint16_t logBufferFilled; |
|||
uint16_t logBufferLine; |
|||
uint16_t logBufferMaxLines; |
|||
char *logBuffer; |
|||
|
|||
|
|||
// the header size of the buffer used, e.g. for the SPI command header
|
|||
int BufferOffset; |
|||
virtual int getBufferOffset(void) = 0; |
|||
|
|||
// Send a command to the display (low level function)
|
|||
virtual void sendCommand(uint8_t com) {(void)com;}; |
|||
|
|||
// Connect to the display
|
|||
virtual bool connect() { return false; }; |
|||
|
|||
// Send all the init commands
|
|||
virtual void sendInitCommands(); |
|||
|
|||
// converts utf8 characters to extended ascii
|
|||
char* utf8ascii(const String &s); |
|||
|
|||
void inline drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *data, uint16_t offset, uint16_t bytesInData) __attribute__((always_inline)); |
|||
|
|||
uint16_t drawStringInternal(int16_t xMove, int16_t yMove, const char* text, uint16_t textLength, uint16_t textWidth, bool utf8); |
|||
|
|||
FontTableLookupFunction fontTableLookupFunction; |
|||
}; |
|||
|
|||
#endif |
|||
File diff suppressed because it is too large
@ -0,0 +1,13 @@ |
|||
#ifndef OLEDDISPLAYFONTS_h |
|||
#define OLEDDISPLAYFONTS_h |
|||
|
|||
#ifdef ARDUINO |
|||
#include <Arduino.h> |
|||
#elif __MBED__ |
|||
#define PROGMEM |
|||
#endif |
|||
|
|||
extern const uint8_t ArialMT_Plain_10[] PROGMEM; |
|||
extern const uint8_t ArialMT_Plain_16[] PROGMEM; |
|||
extern const uint8_t ArialMT_Plain_24[] PROGMEM; |
|||
#endif |
|||
@ -0,0 +1,91 @@ |
|||
#include "SH1106Display.h" |
|||
#include <Adafruit_GrayOLED.h> |
|||
#include "Adafruit_SH110X.h" |
|||
|
|||
bool SH1106Display::i2c_probe(TwoWire &wire, uint8_t addr) |
|||
{ |
|||
wire.beginTransmission(addr); |
|||
uint8_t error = wire.endTransmission(); |
|||
return (error == 0); |
|||
} |
|||
|
|||
bool SH1106Display::begin() |
|||
{ |
|||
return display.begin(DISPLAY_ADDRESS, true) && i2c_probe(Wire, DISPLAY_ADDRESS); |
|||
} |
|||
|
|||
void SH1106Display::turnOn() |
|||
{ |
|||
display.oled_command(SH110X_DISPLAYON); |
|||
_isOn = true; |
|||
} |
|||
|
|||
void SH1106Display::turnOff() |
|||
{ |
|||
display.oled_command(SH110X_DISPLAYOFF); |
|||
_isOn = false; |
|||
} |
|||
|
|||
void SH1106Display::clear() |
|||
{ |
|||
display.clearDisplay(); |
|||
display.display(); |
|||
} |
|||
|
|||
void SH1106Display::startFrame(Color bkg) |
|||
{ |
|||
display.clearDisplay(); // TODO: apply 'bkg'
|
|||
_color = SH110X_WHITE; |
|||
display.setTextColor(_color); |
|||
display.setTextSize(1); |
|||
display.cp437(true); // Use full 256 char 'Code Page 437' font
|
|||
} |
|||
|
|||
void SH1106Display::setTextSize(int sz) |
|||
{ |
|||
display.setTextSize(sz); |
|||
} |
|||
|
|||
void SH1106Display::setColor(Color c) |
|||
{ |
|||
_color = (c != 0) ? SH110X_WHITE : SH110X_BLACK; |
|||
display.setTextColor(_color); |
|||
} |
|||
|
|||
void SH1106Display::setCursor(int x, int y) |
|||
{ |
|||
display.setCursor(x, y); |
|||
} |
|||
|
|||
void SH1106Display::print(const char *str) |
|||
{ |
|||
display.print(str); |
|||
} |
|||
|
|||
void SH1106Display::fillRect(int x, int y, int w, int h) |
|||
{ |
|||
display.fillRect(x, y, w, h, _color); |
|||
} |
|||
|
|||
void SH1106Display::drawRect(int x, int y, int w, int h) |
|||
{ |
|||
display.drawRect(x, y, w, h, _color); |
|||
} |
|||
|
|||
void SH1106Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) |
|||
{ |
|||
display.drawBitmap(x, y, bits, w, h, SH110X_WHITE); |
|||
} |
|||
|
|||
uint16_t SH1106Display::getTextWidth(const char *str) |
|||
{ |
|||
int16_t x1, y1; |
|||
uint16_t w, h; |
|||
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); |
|||
return w; |
|||
} |
|||
|
|||
void SH1106Display::endFrame() |
|||
{ |
|||
display.display(); |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
#pragma once |
|||
|
|||
#include "DisplayDriver.h" |
|||
#include <Wire.h> |
|||
#include <Adafruit_GFX.h> |
|||
#define SH110X_NO_SPLASH |
|||
#include <Adafruit_SH110X.h> |
|||
|
|||
#ifndef PIN_OLED_RESET |
|||
#define PIN_OLED_RESET -1 |
|||
#endif |
|||
|
|||
#ifndef DISPLAY_ADDRESS |
|||
#define DISPLAY_ADDRESS 0x3C |
|||
#endif |
|||
|
|||
class SH1106Display : public DisplayDriver |
|||
{ |
|||
Adafruit_SH1106G display; |
|||
bool _isOn; |
|||
uint8_t _color; |
|||
|
|||
bool i2c_probe(TwoWire &wire, uint8_t addr); |
|||
|
|||
public: |
|||
SH1106Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; } |
|||
bool begin(); |
|||
|
|||
bool isOn() override { return _isOn; } |
|||
void turnOn() override; |
|||
void turnOff() override; |
|||
void clear() override; |
|||
void startFrame(Color bkg = DARK) override; |
|||
void setTextSize(int sz) override; |
|||
void setColor(Color c) override; |
|||
void setCursor(int x, int y) override; |
|||
void print(const char *str) override; |
|||
void fillRect(int x, int y, int w, int h) override; |
|||
void drawRect(int x, int y, int w, int h) override; |
|||
void drawXbm(int x, int y, const uint8_t *bits, int w, int h) override; |
|||
uint16_t getTextWidth(const char *str) override; |
|||
void endFrame() override; |
|||
}; |
|||
@ -0,0 +1,130 @@ |
|||
#include "ST7735Display.h" |
|||
|
|||
#ifndef DISPLAY_ROTATION |
|||
#define DISPLAY_ROTATION 2 |
|||
#endif |
|||
|
|||
#define SCALE_X 1.25f // 160 / 128
|
|||
#define SCALE_Y 1.25f // 80 / 64
|
|||
|
|||
bool ST7735Display::i2c_probe(TwoWire& wire, uint8_t addr) { |
|||
return true; |
|||
/*
|
|||
wire.beginTransmission(addr); |
|||
uint8_t error = wire.endTransmission(); |
|||
return (error == 0); |
|||
*/ |
|||
} |
|||
|
|||
bool ST7735Display::begin() { |
|||
if (!_isOn) { |
|||
if (_peripher_power) _peripher_power->claim(); |
|||
|
|||
pinMode(PIN_TFT_LEDA_CTL, OUTPUT); |
|||
digitalWrite(PIN_TFT_LEDA_CTL, HIGH); |
|||
digitalWrite(PIN_TFT_RST, HIGH); |
|||
|
|||
display.initR(INITR_MINI160x80_PLUGIN); |
|||
display.setRotation(DISPLAY_ROTATION); |
|||
display.setSPISpeed(40000000); |
|||
display.fillScreen(ST77XX_BLACK); |
|||
display.setTextColor(ST77XX_WHITE); |
|||
display.setTextSize(2); |
|||
display.cp437(true); // Use full 256 char 'Code Page 437' font
|
|||
|
|||
_isOn = true; |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
void ST7735Display::turnOn() { |
|||
ST7735Display::begin(); |
|||
} |
|||
|
|||
void ST7735Display::turnOff() { |
|||
if (_isOn) { |
|||
digitalWrite(PIN_TFT_LEDA_CTL, HIGH); |
|||
digitalWrite(PIN_TFT_RST, LOW); |
|||
digitalWrite(PIN_TFT_LEDA_CTL, LOW); |
|||
_isOn = false; |
|||
|
|||
if (_peripher_power) _peripher_power->release(); |
|||
} |
|||
} |
|||
|
|||
void ST7735Display::clear() { |
|||
//Serial.println("DBG: display.Clear");
|
|||
display.fillScreen(ST77XX_BLACK); |
|||
} |
|||
|
|||
void ST7735Display::startFrame(Color bkg) { |
|||
display.fillScreen(0x00); |
|||
display.setTextColor(ST77XX_WHITE); |
|||
display.setTextSize(1); // This one affects size of Please wait... message
|
|||
display.cp437(true); // Use full 256 char 'Code Page 437' font
|
|||
} |
|||
|
|||
void ST7735Display::setTextSize(int sz) { |
|||
display.setTextSize(sz); |
|||
} |
|||
|
|||
void ST7735Display::setColor(Color c) { |
|||
switch (c) { |
|||
case DisplayDriver::DARK : |
|||
_color = ST77XX_BLACK; |
|||
break; |
|||
case DisplayDriver::LIGHT : |
|||
_color = ST77XX_WHITE; |
|||
break; |
|||
case DisplayDriver::RED : |
|||
_color = ST77XX_RED; |
|||
break; |
|||
case DisplayDriver::GREEN : |
|||
_color = ST77XX_GREEN; |
|||
break; |
|||
case DisplayDriver::BLUE : |
|||
_color = ST77XX_BLUE; |
|||
break; |
|||
case DisplayDriver::YELLOW : |
|||
_color = ST77XX_YELLOW; |
|||
break; |
|||
case DisplayDriver::ORANGE : |
|||
_color = ST77XX_ORANGE; |
|||
break; |
|||
default: |
|||
_color = ST77XX_WHITE; |
|||
break; |
|||
} |
|||
display.setTextColor(_color); |
|||
} |
|||
|
|||
void ST7735Display::setCursor(int x, int y) { |
|||
display.setCursor(x*SCALE_X, y*SCALE_Y); |
|||
} |
|||
|
|||
void ST7735Display::print(const char* str) { |
|||
display.print(str); |
|||
} |
|||
|
|||
void ST7735Display::fillRect(int x, int y, int w, int h) { |
|||
display.fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _color); |
|||
} |
|||
|
|||
void ST7735Display::drawRect(int x, int y, int w, int h) { |
|||
display.drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _color); |
|||
} |
|||
|
|||
void ST7735Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { |
|||
display.drawBitmap(x*SCALE_X, y*SCALE_Y, bits, w, h, _color); |
|||
} |
|||
|
|||
uint16_t ST7735Display::getTextWidth(const char* str) { |
|||
int16_t x1, y1; |
|||
uint16_t w, h; |
|||
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); |
|||
return w / SCALE_X; |
|||
} |
|||
|
|||
void ST7735Display::endFrame() { |
|||
// display.display();
|
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
#pragma once |
|||
|
|||
#include "DisplayDriver.h" |
|||
#include <Wire.h> |
|||
#include <SPI.h> |
|||
#include <Adafruit_GFX.h> |
|||
#include <Adafruit_ST7735.h> |
|||
#include <helpers/RefCountedDigitalPin.h> |
|||
|
|||
class ST7735Display : public DisplayDriver { |
|||
Adafruit_ST7735 display; |
|||
bool _isOn; |
|||
uint16_t _color; |
|||
RefCountedDigitalPin* _peripher_power; |
|||
|
|||
bool i2c_probe(TwoWire& wire, uint8_t addr); |
|||
public: |
|||
#ifdef USE_PIN_TFT |
|||
ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), |
|||
display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST), |
|||
_peripher_power(peripher_power) |
|||
{ |
|||
_isOn = false; |
|||
} |
|||
#else |
|||
ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), |
|||
display(&SPI1, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST), |
|||
_peripher_power(peripher_power) |
|||
{ |
|||
_isOn = false; |
|||
} |
|||
#endif |
|||
bool begin(); |
|||
|
|||
bool isOn() override { return _isOn; } |
|||
void turnOn() override; |
|||
void turnOff() override; |
|||
void clear() override; |
|||
void startFrame(Color bkg = DARK) override; |
|||
void setTextSize(int sz) override; |
|||
void setColor(Color c) override; |
|||
void setCursor(int x, int y) override; |
|||
void print(const char* str) override; |
|||
void fillRect(int x, int y, int w, int h) override; |
|||
void drawRect(int x, int y, int w, int h) override; |
|||
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; |
|||
uint16_t getTextWidth(const char* str) override; |
|||
void endFrame() override; |
|||
}; |
|||
@ -0,0 +1,464 @@ |
|||
/**
|
|||
* The MIT License (MIT) |
|||
* |
|||
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn |
|||
* Copyright (c) 2018 by Fabrice Weinberg |
|||
* Copyright (c) 2024 by Heltec AutoMation |
|||
* Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
* of this software and associated documentation files (the "Software"), to deal |
|||
* in the Software without restriction, including without limitation the rights |
|||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
* copies of the Software, and to permit persons to whom the Software is |
|||
* furnished to do so, subject to the following conditions: |
|||
* |
|||
* The above copyright notice and this permission notice shall be included in all |
|||
* copies or substantial portions of the Software. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
* SOFTWARE. |
|||
* |
|||
* ThingPulse invests considerable time and money to develop these open source libraries. |
|||
* Please support us by buying our products (and not the clones) from |
|||
* https://thingpulse.com
|
|||
* |
|||
*/ |
|||
|
|||
#ifndef ST7789Spi_h |
|||
#define ST7789Spi_h |
|||
|
|||
#include "OLEDDisplay.h" |
|||
#include <SPI.h> |
|||
|
|||
|
|||
#define ST_CMD_DELAY 0x80 // special signifier for command lists
|
|||
|
|||
#define ST77XX_NOP 0x00 |
|||
#define ST77XX_SWRESET 0x01 |
|||
#define ST77XX_RDDID 0x04 |
|||
#define ST77XX_RDDST 0x09 |
|||
|
|||
#define ST77XX_SLPIN 0x10 |
|||
#define ST77XX_SLPOUT 0x11 |
|||
#define ST77XX_PTLON 0x12 |
|||
#define ST77XX_NORON 0x13 |
|||
|
|||
#define ST77XX_INVOFF 0x20 |
|||
#define ST77XX_INVON 0x21 |
|||
#define ST77XX_DISPOFF 0x28 |
|||
#define ST77XX_DISPON 0x29 |
|||
#define ST77XX_CASET 0x2A |
|||
#define ST77XX_RASET 0x2B |
|||
#define ST77XX_RAMWR 0x2C |
|||
#define ST77XX_RAMRD 0x2E |
|||
|
|||
#define ST77XX_PTLAR 0x30 |
|||
#define ST77XX_TEOFF 0x34 |
|||
#define ST77XX_TEON 0x35 |
|||
#define ST77XX_MADCTL 0x36 |
|||
#define ST77XX_COLMOD 0x3A |
|||
|
|||
#define ST77XX_MADCTL_MY 0x80 |
|||
#define ST77XX_MADCTL_MX 0x40 |
|||
#define ST77XX_MADCTL_MV 0x20 |
|||
#define ST77XX_MADCTL_ML 0x10 |
|||
#define ST77XX_MADCTL_RGB 0x00 |
|||
|
|||
#define ST77XX_RDID1 0xDA |
|||
#define ST77XX_RDID2 0xDB |
|||
#define ST77XX_RDID3 0xDC |
|||
#define ST77XX_RDID4 0xDD |
|||
|
|||
// Some ready-made 16-bit ('565') color settings:
|
|||
#define ST77XX_BLACK 0x0000 |
|||
#define ST77XX_WHITE 0xFFFF |
|||
#define ST77XX_RED 0xF800 |
|||
#define ST77XX_GREEN 0x07E0 |
|||
#define ST77XX_BLUE 0x001F |
|||
#define ST77XX_CYAN 0x07FF |
|||
#define ST77XX_MAGENTA 0xF81F |
|||
#define ST77XX_YELLOW 0xFFE0 |
|||
#define ST77XX_ORANGE 0xFC00 |
|||
|
|||
#define LED_A_ON LOW |
|||
|
|||
#ifdef ESP_PLATFORM |
|||
#undef LED_A_ON |
|||
#define LED_A_ON HIGH |
|||
#define rtos_free free |
|||
#define rtos_malloc malloc |
|||
//SPIClass SPI1(HSPI);
|
|||
#endif |
|||
class ST7789Spi : public OLEDDisplay { |
|||
private: |
|||
uint8_t _rst; |
|||
uint8_t _dc; |
|||
uint8_t _cs; |
|||
uint8_t _ledA; |
|||
int _miso; |
|||
int _mosi; |
|||
int _clk; |
|||
SPIClass * _spi; |
|||
SPISettings _spiSettings; |
|||
uint16_t _RGB=0xFFFF; |
|||
uint8_t _buffheight; |
|||
public: |
|||
/* pass _cs as -1 to indicate "do not use CS pin", for cases where it is hard wired low */ |
|||
ST7789Spi(SPIClass *spiClass,uint8_t _rst, uint8_t _dc, uint8_t _cs, OLEDDISPLAY_GEOMETRY g = GEOMETRY_RAWMODE,uint16_t width=240,uint16_t height=135,int mosi=-1,int miso=-1,int clk=-1) { |
|||
this->_spi = spiClass; |
|||
this->_rst = _rst; |
|||
this->_dc = _dc; |
|||
this->_cs = _cs; |
|||
this->_mosi=mosi; |
|||
this->_miso=miso; |
|||
this->_clk=clk; |
|||
//this->_ledA = _ledA;
|
|||
_spiSettings = SPISettings(40000000, MSBFIRST, SPI_MODE0); |
|||
setGeometry(g,width,height); |
|||
} |
|||
|
|||
bool connect(){ |
|||
this->_buffheight=displayHeight / 8; |
|||
this->_buffheight+=displayHeight % 8 ? 1:0; |
|||
pinMode(_cs, OUTPUT); |
|||
pinMode(_dc, OUTPUT); |
|||
//pinMode(_ledA, OUTPUT);
|
|||
if (_cs != (uint8_t) -1) { |
|||
pinMode(_cs, OUTPUT); |
|||
} |
|||
pinMode(_rst, OUTPUT); |
|||
|
|||
#ifdef ESP_PLATFORM |
|||
_spi->begin(_clk,_miso,_mosi,-1); |
|||
#else |
|||
_spi->begin(); |
|||
#endif |
|||
_spi->setClockDivider (SPI_CLOCK_DIV2); |
|||
|
|||
// Pulse Reset low for 10ms
|
|||
digitalWrite(_rst, HIGH); |
|||
delay(1); |
|||
digitalWrite(_rst, LOW); |
|||
delay(10); |
|||
digitalWrite(_rst, HIGH); |
|||
_spi->begin (); |
|||
//digitalWrite(_ledA, LED_A_ON);
|
|||
return true; |
|||
} |
|||
|
|||
void display(void) { |
|||
#ifdef OLEDDISPLAY_DOUBLE_BUFFER |
|||
|
|||
uint16_t minBoundY = UINT16_MAX; |
|||
uint16_t maxBoundY = 0; |
|||
|
|||
uint16_t minBoundX = UINT16_MAX; |
|||
uint16_t maxBoundX = 0; |
|||
|
|||
uint16_t x, y; |
|||
|
|||
// Calculate the Y bounding box of changes
|
|||
// and copy buffer[pos] to buffer_back[pos];
|
|||
for (y = 0; y < _buffheight; y++) { |
|||
for (x = 0; x < displayWidth; x++) { |
|||
//Serial.printf("x %d y %d\r\n",x,y);
|
|||
uint16_t pos = x + y * displayWidth; |
|||
if (buffer[pos] != buffer_back[pos]) { |
|||
minBoundY = min(minBoundY, y); |
|||
maxBoundY = max(maxBoundY, y); |
|||
minBoundX = min(minBoundX, x); |
|||
maxBoundX = max(maxBoundX, x); |
|||
} |
|||
buffer_back[pos] = buffer[pos]; |
|||
} |
|||
yield(); |
|||
} |
|||
|
|||
// If the minBoundY wasn't updated
|
|||
// we can savely assume that buffer_back[pos] == buffer[pos]
|
|||
// holdes true for all values of pos
|
|||
if (minBoundY == UINT16_MAX) return; |
|||
|
|||
set_CS(LOW); |
|||
_spi->beginTransaction(_spiSettings); |
|||
|
|||
for (y = minBoundY; y <= maxBoundY; y++) |
|||
{ |
|||
for(int temp = 0; temp<8;temp++) |
|||
{ |
|||
//setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
|
|||
setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); |
|||
//setAddrWindow(y*8+temp,minBoundX,1,maxBoundX-minBoundX+1);
|
|||
uint32_t const pixbufcount = maxBoundX-minBoundX+1; |
|||
uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount); |
|||
for (x = minBoundX; x <= maxBoundX; x++) |
|||
{ |
|||
pixbuf[x-minBoundX] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0; |
|||
} |
|||
#ifdef ESP_PLATFORM |
|||
_spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount); |
|||
#else |
|||
_spi->transfer(pixbuf, NULL, 2 * pixbufcount); |
|||
#endif |
|||
rtos_free(pixbuf); |
|||
} |
|||
} |
|||
_spi->endTransaction(); |
|||
set_CS(HIGH); |
|||
|
|||
#else |
|||
set_CS(LOW); |
|||
_spi->beginTransaction(_spiSettings); |
|||
uint8_t x, y; |
|||
for (y = 0; y < _buffheight; y++) |
|||
{ |
|||
for(int temp = 0; temp<8;temp++) |
|||
{ |
|||
//setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
|
|||
//setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
|
|||
setAddrWindow(y*8+temp,0,1,displayWidth); |
|||
uint32_t const pixbufcount = displayWidth; |
|||
uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount); |
|||
for (x = 0; x < displayWidth; x++) |
|||
{ |
|||
pixbuf[x] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0; |
|||
} |
|||
#ifdef ESP_PLATFORM |
|||
_spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount); |
|||
#else |
|||
_spi->transfer(pixbuf, NULL, 2 * pixbufcount); |
|||
#endif |
|||
rtos_free(pixbuf); |
|||
} |
|||
} |
|||
_spi->endTransaction(); |
|||
set_CS(HIGH); |
|||
|
|||
#endif |
|||
} |
|||
|
|||
virtual void resetOrientation() { |
|||
uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV; |
|||
sendCommand(ST77XX_MADCTL); |
|||
WriteData(madctl); |
|||
delay(10); |
|||
} |
|||
|
|||
virtual void flipScreenVertically() { |
|||
uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MY; |
|||
sendCommand(ST77XX_MADCTL); |
|||
WriteData(madctl); |
|||
delay(10); |
|||
} |
|||
|
|||
virtual void mirrorScreen() { |
|||
uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX|ST77XX_MADCTL_MY; |
|||
sendCommand(ST77XX_MADCTL); |
|||
WriteData(madctl); |
|||
delay(10); |
|||
} |
|||
|
|||
virtual void landscapeScreen() { |
|||
// For landscape mode rotated 180 degrees with correct text direction
|
|||
// MV swaps rows/columns for landscape orientation
|
|||
// Adding MX (instead of MY) flips X axis and rotates 180 degrees
|
|||
uint8_t madctl = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MV | ST77XX_MADCTL_MX; |
|||
sendCommand(ST77XX_MADCTL); |
|||
WriteData(madctl); |
|||
delay(10); |
|||
} |
|||
|
|||
|
|||
void setRGB(uint16_t c) |
|||
{ |
|||
|
|||
this->_RGB=0x00|c>>8|c<<8&0xFF00; |
|||
} |
|||
|
|||
void displayOn(void) { |
|||
//sendCommand(DISPLAYON);
|
|||
} |
|||
|
|||
void displayOff(void) { |
|||
//sendCommand(DISPLAYOFF);
|
|||
} |
|||
|
|||
void drawBitmap(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *xbm) { |
|||
int16_t widthInXbm = (width + 7) / 8; |
|||
uint8_t data = 0; |
|||
|
|||
for(int16_t y = 0; y < height; y++) { |
|||
for(int16_t x = 0; x < width; x++ ) { |
|||
if (x & 7) { |
|||
data <<= 1; // Move a bit
|
|||
} else { // Read new data every 8 bit
|
|||
data = pgm_read_byte(xbm + (x / 8) + y * widthInXbm); |
|||
} |
|||
// if there is a bit draw it
|
|||
if (data & 0x80) { |
|||
setPixel(xMove + x, yMove + y); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
//#define ST77XX_MADCTL_MY 0x80
|
|||
//#define ST77XX_MADCTL_MX 0x40
|
|||
//#define ST77XX_MADCTL_MV 0x20
|
|||
//#define ST77XX_MADCTL_ML 0x10
|
|||
protected: |
|||
// Send all the init commands
|
|||
virtual void sendInitCommands() |
|||
{ |
|||
sendCommand(ST77XX_SWRESET); // 1: Software reset, no args, w/delay
|
|||
delay(150); |
|||
|
|||
sendCommand(ST77XX_SLPOUT); // 2: Out of sleep mode, no args, w/delay
|
|||
delay(10); |
|||
|
|||
sendCommand(ST77XX_COLMOD); // 3: Set color mode, 16-bit color
|
|||
WriteData(0x55); |
|||
delay(10); |
|||
|
|||
// Initialize with landscape orientation rotated 180 degrees
|
|||
uint8_t madctl = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MV | ST77XX_MADCTL_MX; |
|||
sendCommand(ST77XX_MADCTL); // 4: Mem access ctrl (directions)
|
|||
WriteData(madctl); |
|||
delay(10); |
|||
|
|||
// Set column address range for landscape orientation (240 pixels wide)
|
|||
sendCommand(ST77XX_CASET); // 5: Column addr set
|
|||
WriteData(0x00); |
|||
WriteData(0x00); // XSTART = 0
|
|||
WriteData(0x00); |
|||
WriteData(240); // XEND = 240
|
|||
|
|||
// Set row address range for landscape orientation (135 pixels tall)
|
|||
sendCommand(ST77XX_RASET); // 6: Row addr set
|
|||
WriteData(0x00); |
|||
WriteData(0x00); // YSTART = 0
|
|||
WriteData(0x00); |
|||
WriteData(135); // YEND = 135
|
|||
|
|||
sendCommand(ST77XX_SLPOUT); // 7: hack
|
|||
delay(10); |
|||
|
|||
sendCommand(ST77XX_NORON); // 8: Normal display on, no args, w/delay
|
|||
delay(10); |
|||
|
|||
sendCommand(ST77XX_DISPON); // 9: Main screen turn on, no args, delay
|
|||
delay(10); |
|||
|
|||
sendCommand(ST77XX_INVON); // 10: invert
|
|||
delay(10); |
|||
|
|||
// Use landscape mode instead of portrait
|
|||
landscapeScreen(); |
|||
setRGB(ST77XX_GREEN); |
|||
} |
|||
|
|||
|
|||
private: |
|||
|
|||
void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { |
|||
x += (320-displayWidth)/2; |
|||
y += (240-displayHeight)/2; |
|||
|
|||
uint32_t xa = ((uint32_t)x << 16) | (x + w - 1); |
|||
uint32_t ya = ((uint32_t)y << 16) | (y + h - 1); |
|||
|
|||
writeCommand(ST77XX_CASET); // Column addr set
|
|||
SPI_WRITE32(xa); |
|||
|
|||
writeCommand(ST77XX_RASET); // Row addr set
|
|||
SPI_WRITE32(ya); |
|||
|
|||
writeCommand(ST77XX_RAMWR); // write to RAM
|
|||
} |
|||
int getBufferOffset(void) { |
|||
return 0; |
|||
} |
|||
inline void set_CS(bool level) { |
|||
if (_cs != (uint8_t) -1) { |
|||
digitalWrite(_cs, level); |
|||
} |
|||
}; |
|||
inline void sendCommand(uint8_t com) __attribute__((always_inline)){ |
|||
set_CS(HIGH); |
|||
digitalWrite(_dc, LOW); |
|||
set_CS(LOW); |
|||
_spi->beginTransaction(_spiSettings); |
|||
_spi->transfer(com); |
|||
_spi->endTransaction(); |
|||
set_CS(HIGH); |
|||
digitalWrite(_dc, HIGH); |
|||
} |
|||
|
|||
inline void WriteData(uint8_t data) __attribute__((always_inline)){ |
|||
digitalWrite(_cs, LOW); |
|||
_spi->beginTransaction(_spiSettings); |
|||
_spi->transfer(data); |
|||
_spi->endTransaction(); |
|||
digitalWrite(_cs, HIGH); |
|||
} |
|||
void SPI_WRITE32(uint32_t l) |
|||
{ |
|||
_spi->transfer(l >> 24); |
|||
_spi->transfer(l >> 16); |
|||
_spi->transfer(l >> 8); |
|||
_spi->transfer(l); |
|||
} |
|||
void writeCommand(uint8_t cmd) { |
|||
digitalWrite(_dc, LOW); |
|||
_spi->transfer(cmd); |
|||
digitalWrite(_dc, HIGH); |
|||
} |
|||
|
|||
// Private functions
|
|||
void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) { |
|||
this->geometry = g; |
|||
|
|||
switch (g) { |
|||
case GEOMETRY_128_128: |
|||
this->displayWidth = 128; |
|||
this->displayHeight = 128; |
|||
break; |
|||
case GEOMETRY_128_64: |
|||
this->displayWidth = 128; |
|||
this->displayHeight = 64; |
|||
break; |
|||
case GEOMETRY_128_32: |
|||
this->displayWidth = 128; |
|||
this->displayHeight = 32; |
|||
break; |
|||
case GEOMETRY_64_48: |
|||
this->displayWidth = 64; |
|||
this->displayHeight = 48; |
|||
break; |
|||
case GEOMETRY_64_32: |
|||
this->displayWidth = 64; |
|||
this->displayHeight = 32; |
|||
break; |
|||
case GEOMETRY_RAWMODE: |
|||
this->displayWidth = width > 0 ? width : 128; |
|||
this->displayHeight = height > 0 ? height : 64; |
|||
break; |
|||
} |
|||
uint8_t tmp=displayHeight % 8; |
|||
uint8_t _buffheight=displayHeight / 8; |
|||
|
|||
if(tmp!=0) |
|||
_buffheight++; |
|||
this->displayBufferSize = displayWidth * _buffheight ; |
|||
} |
|||
|
|||
|
|||
|
|||
}; |
|||
|
|||
#endif |
|||
@ -0,0 +1,54 @@ |
|||
#ifdef PIN_BUZZER |
|||
#include "buzzer.h" |
|||
|
|||
void genericBuzzer::begin() { |
|||
// Serial.print("DBG: Setting up buzzer on pin ");
|
|||
// Serial.println(PIN_BUZZER);
|
|||
#ifdef PIN_BUZZER_EN |
|||
pinMode(PIN_BUZZER_EN, OUTPUT); |
|||
digitalWrite(PIN_BUZZER_EN, HIGH); |
|||
#endif |
|||
|
|||
quiet(false); |
|||
pinMode(PIN_BUZZER, OUTPUT); |
|||
startup(); |
|||
} |
|||
|
|||
void genericBuzzer::play(const char *melody) { |
|||
if (isPlaying()) // interrupt existing
|
|||
{ |
|||
rtttl::stop(); |
|||
} |
|||
|
|||
if (_is_quiet) return; |
|||
|
|||
rtttl::begin(PIN_BUZZER,melody); |
|||
// Serial.print("DBG: Playing melody - isQuiet: ");
|
|||
// Serial.println(isQuiet());
|
|||
} |
|||
|
|||
bool genericBuzzer::isPlaying() { |
|||
return rtttl::isPlaying(); |
|||
} |
|||
|
|||
void genericBuzzer::loop() { |
|||
if (!rtttl::done()) rtttl::play(); |
|||
} |
|||
|
|||
void genericBuzzer::startup() { |
|||
play(startup_song); |
|||
} |
|||
|
|||
void genericBuzzer::shutdown() { |
|||
play(shutdown_song); |
|||
} |
|||
|
|||
void genericBuzzer::quiet(bool buzzer_state) { |
|||
_is_quiet = buzzer_state; |
|||
} |
|||
|
|||
bool genericBuzzer::isQuiet() { |
|||
return _is_quiet; |
|||
} |
|||
|
|||
#endif // ifdef PIN_BUZZER
|
|||
@ -0,0 +1,37 @@ |
|||
#pragma once |
|||
|
|||
#include <Arduino.h> |
|||
#include <NonBlockingRtttl.h> |
|||
|
|||
/* class abstracts underlying RTTTL library
|
|||
|
|||
Just a simple imlementation to start. At the moment use same |
|||
melody for message and discovery |
|||
Suggest enum type for different sounds |
|||
- on message |
|||
- on discovery |
|||
|
|||
TODO |
|||
- make message ring tone configurable |
|||
|
|||
*/ |
|||
|
|||
class genericBuzzer |
|||
{ |
|||
public: |
|||
void begin(); // set up buzzer port
|
|||
void play(const char *melody); // Generic play function
|
|||
void loop(); // loop driven-nonblocking
|
|||
void startup(); // play startup sound
|
|||
void shutdown(); // play shutdown sound
|
|||
bool isPlaying(); // returns true if a sound is still playing else false
|
|||
void quiet(bool buzzer_state); // enables or disables the buzzer
|
|||
bool isQuiet(); // get buzzer state on/off
|
|||
|
|||
private: |
|||
// gemini's picks:
|
|||
const char *startup_song = "Startup:d=4,o=5,b=160:16c6,16e6,8g6"; |
|||
const char *shutdown_song = "Shutdown:d=4,o=5,b=100:8g5,16e5,16c5"; |
|||
|
|||
bool _is_quiet = true; |
|||
}; |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue