diff options
author | joeycastillo <joeycastillo@utexas.edu> | 2022-08-03 11:56:52 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-03 11:56:52 -0600 |
commit | e790a025787e0e1aa59b98b95e194cf4318d1578 (patch) | |
tree | 149bd53bbd46ebddd31957cc827a7c40ccf6f1a0 | |
parent | 6d87f5a6268a9a516d8c577dfd71b39a5bfc384a (diff) | |
parent | bcd3b666848214a735f37a5a4f08b157ba7bb3a1 (diff) | |
download | Sensor-Watch-e790a025787e0e1aa59b98b95e194cf4318d1578.tar.gz Sensor-Watch-e790a025787e0e1aa59b98b95e194cf4318d1578.tar.bz2 Sensor-Watch-e790a025787e0e1aa59b98b95e194cf4318d1578.zip |
Merge pull request #80 from joeycastillo/lfs
Movement: add a lil file system with lfs
25 files changed, 870 insertions, 13 deletions
diff --git a/.gitmodules b/.gitmodules index 89e424a9..54c3e634 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "tinyusb"] path = tinyusb url = https://github.com/hathach/tinyusb.git +[submodule "littlefs"] + path = littlefs + url = https://github.com/littlefs-project/littlefs.git @@ -5,7 +5,7 @@ #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = "Sensor Watch" -PROJECT_NUMBER = "0.0.1" +PROJECT_NUMBER = "0.0.2" PROJECT_BRIEF = "A board replacement for the classic Casio F-91W wristwatch, powered by a Microchip SAM L22 microcontroller." PROJECT_LOGO = OUTPUT_DIRECTORY = "." diff --git a/apps/eeprom-emulation-upgrade/Makefile b/apps/eeprom-emulation-upgrade/Makefile new file mode 100755 index 00000000..5534c178 --- /dev/null +++ b/apps/eeprom-emulation-upgrade/Makefile @@ -0,0 +1,10 @@ +TOP = ../.. +include $(TOP)/make.mk + +INCLUDES += \ + -I./ + +SRCS += \ + ./app.c + +include $(TOP)/rules.mk diff --git a/apps/eeprom-emulation-upgrade/app.c b/apps/eeprom-emulation-upgrade/app.c new file mode 100644 index 00000000..edd73e44 --- /dev/null +++ b/apps/eeprom-emulation-upgrade/app.c @@ -0,0 +1,62 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <peripheral_clk_config.h> +#include "watch.h" + +void app_init(void) { +} + +void app_wake_from_backup(void) { +} + +void app_setup(void) { + delay_ms(5000); + while (!(NVMCTRL->INTFLAG.reg & NVMCTRL_INTFLAG_READY)); + uint32_t user_row = (*((uint32_t *)NVMCTRL_AUX0_ADDRESS)); + uint8_t eeprom = (user_row >> NVMCTRL_FUSES_EEPROM_SIZE_Pos) & 7; + printf("User row read successfully: 0x%lx\n", user_row); + + if (eeprom != 1) { + user_row &= ~NVMCTRL_FUSES_EEPROM_SIZE_Msk; + user_row |= NVMCTRL_FUSES_EEPROM_SIZE(1); + if (NVMCTRL->STATUS.reg & NVMCTRL_STATUS_SB) { + printf("Secured bit was set; cannot perform upgrade.\n"); + return; + } + printf("EEPROM configuration was %d.\nApplying change...\n", eeprom); + + uint32_t temp = NVMCTRL->CTRLB.reg; // Backup settings + NVMCTRL->CTRLB.reg = temp | NVMCTRL_CTRLB_CACHEDIS; // Disable Cache + NVMCTRL->STATUS.reg |= NVMCTRL_STATUS_MASK; // Clear error flags + NVMCTRL->ADDR.reg = NVMCTRL_AUX0_ADDRESS / 2; // Set address, command will be issued elsewhere + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_EAR | NVMCTRL_CTRLA_CMDEX_KEY; // Erase the user page + while (!(NVMCTRL->INTFLAG.reg & NVMCTRL_INTFLAG_READY)); // Wait for NVM command to complete + NVMCTRL->STATUS.reg |= NVMCTRL_STATUS_MASK; // Clear error flags + NVMCTRL->ADDR.reg = NVMCTRL_AUX0_ADDRESS / 2; // Set address, command will be issued elsewhere + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_PBC | NVMCTRL_CTRLA_CMDEX_KEY; // Erase the page buffer before buffering new data + while (!(NVMCTRL->INTFLAG.reg & NVMCTRL_INTFLAG_READY)); // Wait for NVM command to complete + NVMCTRL->STATUS.reg |= NVMCTRL_STATUS_MASK; // Clear error flags + NVMCTRL->ADDR.reg = NVMCTRL_AUX0_ADDRESS / 2; // Set address, command will be issued elsewhere + *((uint32_t *)NVMCTRL_AUX0_ADDRESS) = user_row; // write the new fuse values to the memory buffer + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_WAP | NVMCTRL_CTRLA_CMDEX_KEY; // Write the user page + NVMCTRL->CTRLB.reg = temp; // Restore settings + + printf("Done! Resetting...\n"); + delay_ms(1000); + NVIC_SystemReset(); + } else { + printf("EEPROM configuration was %d (8192 bytes). Upgrade successful!\n", eeprom); + } + printf("%d %d\n", eeprom, NVMCTRL->PARAM.bit.RWWEEP); +} + +void app_prepare_for_standby(void) { +} + +void app_wake_from_standby(void) { +} + +bool app_loop(void) { + return true; +} diff --git a/apps/flash-test/Makefile b/apps/flash-test/Makefile new file mode 100755 index 00000000..1e552265 --- /dev/null +++ b/apps/flash-test/Makefile @@ -0,0 +1,13 @@ +TOP = ../.. +include $(TOP)/make.mk + +INCLUDES += \ + -I$(TOP)/littlefs/ \ + -I./ + +SRCS += \ + $(TOP)/littlefs/lfs.c \ + $(TOP)/littlefs/lfs_util.c \ + ./app.c + +include $(TOP)/rules.mk diff --git a/apps/flash-test/app.c b/apps/flash-test/app.c new file mode 100644 index 00000000..17b1d0aa --- /dev/null +++ b/apps/flash-test/app.c @@ -0,0 +1,131 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <peripheral_clk_config.h> +#include "watch.h" +#include "lfs.h" +#include "hpl_flash.h" + +int lfs_storage_read(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size); +int lfs_storage_prog(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size); +int lfs_storage_erase(const struct lfs_config *cfg, lfs_block_t block); +int lfs_storage_sync(const struct lfs_config *cfg); + +int lfs_storage_read(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { + (void) cfg; + return !watch_storage_read(block, off, (void *)buffer, size); +} + +int lfs_storage_prog(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { + (void) cfg; + return !watch_storage_write(block, off, (void *)buffer, size); +} + +int lfs_storage_erase(const struct lfs_config *cfg, lfs_block_t block) { + (void) cfg; + return !watch_storage_erase(block); +} + +int lfs_storage_sync(const struct lfs_config *cfg) { + (void) cfg; + return !watch_storage_sync(); +} + +const struct lfs_config cfg = { + // block device operations + .read = lfs_storage_read, + .prog = lfs_storage_prog, + .erase = lfs_storage_erase, + .sync = lfs_storage_sync, + + // block device configuration + .read_size = 16, + .prog_size = NVMCTRL_PAGE_SIZE, + .block_size = NVMCTRL_ROW_SIZE, + .block_count = NVMCTRL_RWWEE_PAGES / 4, + .cache_size = NVMCTRL_PAGE_SIZE, + .lookahead_size = 16, + .block_cycles = 100, +}; + +lfs_t lfs; +lfs_file_t file; + +static int _traverse_df_cb(void *p, lfs_block_t block){ + (void) block; + uint32_t *nb = p; + *nb += 1; + return 0; +} + +static int get_free_space(void){ + int err; + + uint32_t free_blocks = 0; + err = lfs_fs_traverse(&lfs, _traverse_df_cb, &free_blocks); + if(err < 0){ + return err; + } + + uint32_t available = cfg.block_count * cfg.block_size - free_blocks * cfg.block_size; + + return available; +} + +static void cb_tick(void) { + watch_date_time date_time = watch_rtc_get_date_time(); + if (date_time.unit.second == 0) { + int err = lfs_mount(&lfs, &cfg); + if (err) { + printf("Mount failed: %d\n", err); + } + // read current count + uint32_t loop_count = 0; + lfs_file_open(&lfs, &file, "loop_count", LFS_O_RDWR | LFS_O_CREAT); + lfs_file_read(&lfs, &file, &loop_count, sizeof(loop_count)); + + // update loop count + loop_count += 1; + lfs_file_rewind(&lfs, &file); + lfs_file_write(&lfs, &file, &loop_count, sizeof(loop_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("loop_count: %ld\n", loop_count); + printf("free space: %d\n", get_free_space()); + } +} + +void app_init(void) { +} + +void app_wake_from_backup(void) { +} + +void app_setup(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); + } + watch_rtc_register_tick_callback(cb_tick); +} + +void app_prepare_for_standby(void) { +} + +void app_wake_from_standby(void) { +} + +bool app_loop(void) { + return true; +} diff --git a/littlefs b/littlefs new file mode 160000 +Subproject 40dba4a556e0d81dfbe64301a6aa4e18ceca896 @@ -113,6 +113,7 @@ SRCS += \ $(TOP)/watch-library/hardware/watch/watch_i2c.c \ $(TOP)/watch-library/hardware/watch/watch_spi.c \ $(TOP)/watch-library/hardware/watch/watch_uart.c \ + $(TOP)/watch-library/hardware/watch/watch_storage.c \ $(TOP)/watch-library/hardware/watch/watch_deepsleep.c \ $(TOP)/watch-library/hardware/watch/watch_private.c \ $(TOP)/watch-library/hardware/watch/watch.c \ @@ -184,6 +185,7 @@ SRCS += \ $(TOP)/watch-library/simulator/watch/watch_i2c.c \ $(TOP)/watch-library/simulator/watch/watch_spi.c \ $(TOP)/watch-library/simulator/watch/watch_uart.c \ + $(TOP)/watch-library/simulator/watch/watch_storage.c \ $(TOP)/watch-library/simulator/watch/watch_deepsleep.c \ $(TOP)/watch-library/simulator/watch/watch_private.c \ $(TOP)/watch-library/simulator/watch/watch.c \ diff --git a/movement/filesystem.c b/movement/filesystem.c new file mode 100644 index 00000000..d122a7fa --- /dev/null +++ b/movement/filesystem.c @@ -0,0 +1,245 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <peripheral_clk_config.h> +#include "filesystem.h" +#include "watch.h" +#include "lfs.h" +#include "hpl_flash.h" + +int lfs_storage_read(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size); +int lfs_storage_prog(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size); +int lfs_storage_erase(const struct lfs_config *cfg, lfs_block_t block); +int lfs_storage_sync(const struct lfs_config *cfg); + +int lfs_storage_read(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { + (void) cfg; + return !watch_storage_read(block, off, (void *)buffer, size); +} + +int lfs_storage_prog(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { + (void) cfg; + return !watch_storage_write(block, off, (void *)buffer, size); +} + +int lfs_storage_erase(const struct lfs_config *cfg, lfs_block_t block) { + (void) cfg; + return !watch_storage_erase(block); +} + +int lfs_storage_sync(const struct lfs_config *cfg) { + (void) cfg; + return !watch_storage_sync(); +} + +const struct lfs_config cfg = { + // block device operations + .read = lfs_storage_read, + .prog = lfs_storage_prog, + .erase = lfs_storage_erase, + .sync = lfs_storage_sync, + + // block device configuration + .read_size = 16, + .prog_size = NVMCTRL_PAGE_SIZE, + .block_size = NVMCTRL_ROW_SIZE, + .block_count = NVMCTRL_RWWEE_PAGES / 4, + .cache_size = NVMCTRL_PAGE_SIZE, + .lookahead_size = 16, + .block_cycles = 100, +}; + +static lfs_t lfs; +static lfs_file_t file; +static struct lfs_info info; + +static int _traverse_df_cb(void *p, lfs_block_t block) { + (void) block; + uint32_t *nb = p; + *nb += 1; + return 0; +} + +int32_t filesystem_get_free_space(void) { + int err; + + uint32_t free_blocks = 0; + err = lfs_fs_traverse(&lfs, _traverse_df_cb, &free_blocks); + if(err < 0){ + return err; + } + + uint32_t available = cfg.block_count * cfg.block_size - free_blocks * cfg.block_size; + + return (int32_t)available; +} + +static int filesystem_ls(lfs_t *lfs, const char *path) { + lfs_dir_t dir; + int err = lfs_dir_open(lfs, &dir, path); + if (err < 0) { + return err; + } + + struct lfs_info info; + while (true) { + int res = lfs_dir_read(lfs, &dir, &info); + if (res < 0) { + return res; + } + + if (res == 0) { + break; + } + + switch (info.type) { + case LFS_TYPE_REG: printf("file "); break; + case LFS_TYPE_DIR: printf("dir "); break; + default: printf("? "); break; + } + + printf("%4ld bytes ", info.size); + + printf("%s\n", info.name); + } + + err = lfs_dir_close(lfs, &dir); + if (err < 0) { + return err; + } + + return 0; +} + +bool filesystem_init(void) { + int err = lfs_mount(&lfs, &cfg); + + // reformat if we can't mount the filesystem + // this should only happen on the first boot + if (err < 0) { + printf("Ignore that error! Formatting filesystem...\n"); + err = lfs_format(&lfs, &cfg); + if (err < 0) return false; + err = lfs_mount(&lfs, &cfg) == LFS_ERR_OK; + printf("Filesystem mounted with %ld bytes free.\n", filesystem_get_free_space()); + } + + return err == LFS_ERR_OK; +} + +bool filesystem_file_exists(char *filename) { + info.type = 0; + lfs_stat(&lfs, filename, &info); + return info.type == LFS_TYPE_REG; +} + +bool filesystem_rm(char *filename) { + info.type = 0; + lfs_stat(&lfs, filename, &info); + if (filesystem_file_exists(filename)) { + return lfs_remove(&lfs, filename) == LFS_ERR_OK; + } else { + printf("rm: %s: No such file\n", filename); + return false; + } +} + +int32_t filesystem_get_file_size(char *filename) { + if (filesystem_file_exists(filename)) { + return info.size; // info struct was just populated by filesystem_file_exists + } + + return -1; +} + +bool filesystem_read_file(char *filename, char *buf, int32_t length) { + memset(buf, 0, length); + int32_t file_size = filesystem_get_file_size(filename); + if (file_size > 0) { + int err = lfs_file_open(&lfs, &file, filename, LFS_O_RDONLY); + if (err < 0) return false; + err = lfs_file_read(&lfs, &file, buf, min(length, file_size)); + if (err < 0) return false; + return lfs_file_close(&lfs, &file) == LFS_ERR_OK; + } + + return false; +} + +static void filesystem_cat(char *filename) { + info.type = 0; + lfs_stat(&lfs, filename, &info); + if (filesystem_file_exists(filename)) { + if (info.size > 0) { + char *buf = malloc(info.size + 1); + filesystem_read_file(filename, buf, info.size); + printf("%s\n", buf); + free(buf); + } else { + printf("\n"); + } + } else { + printf("cat: %s: No such file\n", filename); + } +} + +bool filesystem_write_file(char *filename, char *text, int32_t length) { + int err = lfs_file_open(&lfs, &file, filename, LFS_O_RDWR | LFS_O_CREAT | LFS_O_TRUNC); + if (err < 0) return false; + err = lfs_file_write(&lfs, &file, text, length); + if (err < 0) return false; + return lfs_file_close(&lfs, &file) == LFS_ERR_OK; +} + +void filesystem_process_command(char *line) { + printf("$ %s", line); + char *command = strtok(line, " \n"); + + if (strcmp(command, "ls") == 0) { + char *directory = strtok(NULL, " \n"); + if (directory == NULL) { + filesystem_ls(&lfs, "/"); + } else { + printf("usage: ls\n"); + } + } else if (strcmp(command, "cat") == 0) { + char *filename = strtok(NULL, " \n"); + if (filename == NULL) { + printf("usage: cat file\n"); + } else { + filesystem_cat(filename); + } + } else if (strcmp(command, "df") == 0) { + printf("free space: %ld bytes\n", filesystem_get_free_space()); + } else if (strcmp(command, "rm") == 0) { + char *filename = strtok(NULL, " \n"); + if (filename == NULL) { + printf("usage: rm file\n"); + } else { + filesystem_rm(filename); + } + } else if (strcmp(command, "echo") == 0) { + char *text = malloc(248); + memset(text, 0, 248); + size_t pos = 0; + char *word = strtok(NULL, " \n"); + while (strcmp(word, ">")) { + sprintf(text + pos, "%s ", word); + pos += strlen(word) + 1; + word = strtok(NULL, " \n"); + if (word == NULL) break; + } + text[strlen(text) - 1] = 0; + char *filename = strtok(NULL, " \n"); + if (filename == NULL) { + printf("usage: echo text > file\n"); + } else if (strchr(filename, '/') || strchr(filename, '\\')) { + printf("subdirectories are not supported\n"); + } else { + filesystem_write_file(filename, text, strlen(text)); + } + free(text); + } else { + printf("%s: command not found\n", command); + } +} diff --git a/movement/filesystem.h b/movement/filesystem.h new file mode 100644 index 00000000..b0fb7f58 --- /dev/null +++ b/movement/filesystem.h @@ -0,0 +1,85 @@ +/* + * MIT License + * + * Copyright (c) 2022 Joey Castillo + * + * 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 FILESYSTEM_H_ +#define FILESYSTEM_H_ +#include <stdio.h> +#include <stdbool.h> +#include "watch.h" + +/** @brief Initializes and mounts the tiny 8kb filesystem, formatting it if need be. + * @return true if the filesystem was mounted successfully. + */ +bool filesystem_init(void); + +/** @brief Gets the space available on the filesystem. + * @return the free space in bytes + */ +int32_t filesystem_get_free_space(void); + +/** @brief Checks for the existence of a file on the filesystem. + * @param filename the file you wish to check + * @return true if the file exists; false otherwise + */ +bool filesystem_file_exists(char *filename); + +/** @brief Removes a file on the filesystem. + * @param filename the file you wish to remove + * @return true if the file was deleted successfully; false otherwise + */ +bool filesystem_rm(char *filename); + +/** @brief Gets the size of a file on the filesystem. + * @param filename the file whose size you wish to determine + * @return the file's size in bytes, or -1 if the file does not exist. + */ +int32_t filesystem_get_file_size(char *filename); + +/** @brief Reads a file from the filesystem into a buffer + * @param filename the file you wish to read + * @param buf A buffer of at least length bytes; the file will be read into this buffer + * @param length The number of bytes to read + * @return true if the read was successful; false otherwise + * @note This function will set buf to zero and read all bytes of the file into the buffer. + * If you are reading a raw value (say you wrote a uint32 to a file), you can read back + * the value by passing in the file's length for length. If you wish to treat the buffer + * as a null-terminated string, allocate a buffer one byte longer than the file's length, + * and the last byte will be guaranteed to be 0. + */ +bool filesystem_read_file(char *filename, char *buf, int32_t length); + +/** @brief Writes file to the filesystem + * @param filename the file you wish to write + * @param text The contents of the file + * @param length The number of bytes to write + * @return true if the write was successful; false otherwise + */ +bool filesystem_write_file(char *filename, char *text, int32_t length); + +/** @brief Handles the interactive file browser when Movement is plugged in to USB. + * @param line The command that the user typed into the serial console. + */ +void filesystem_process_command(char *line); + +#endif // FILESYSTEM_H_ diff --git a/movement/make/Makefile b/movement/make/Makefile index 22e5f31b..05f3636c 100755 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -16,6 +16,7 @@ INCLUDES += \ -I../watch_faces/complication/ \ -I../watch_faces/sensor/ \ -I../watch_faces/demo/ \ + -I../../littlefs/ \ -I../lib/TOTP-MCU/ \ -I../lib/sunriset/ \ -I../lib/vsop87/ \ @@ -33,7 +34,10 @@ SRCS += \ ../lib/sunriset/sunriset.c \ ../lib/vsop87/vsop87a_milli.c \ ../lib/astrolib/astrolib.c \ + ../../littlefs/lfs.c \ + ../../littlefs/lfs_util.c \ ../movement.c \ + ../filesystem.c \ ../watch_faces/clock/simple_clock_face.c \ ../watch_faces/clock/world_clock_face.c \ ../watch_faces/clock/beats_face.c \ diff --git a/movement/movement.c b/movement/movement.c index 361a7aa1..d79142ec 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -25,7 +25,11 @@ #include <stdio.h> #include <string.h> #include <limits.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> #include "watch.h" +#include "filesystem.h" #include "movement.h" #ifndef MOVEMENT_FIRMWARE @@ -259,6 +263,8 @@ void app_init(void) { movement_state.next_available_backup_register = 4; _movement_reset_inactivity_countdown(); + filesystem_init(); + #if __EMSCRIPTEN__ int32_t time_zone_offset = EM_ASM_INT({ return -new Date().getTimezoneOffset(); @@ -442,6 +448,32 @@ bool app_loop(void) { } } + // if we are plugged into USB, handle the file browser tasks + if (watch_is_usb_enabled()) { + char line[256] = {0}; +#if __EMSCRIPTEN__ + // This is a terrible hack; ideally this should be handled deeper in the watch library. + // Alas, emscripten treats read() as something that should pop up an input box, so I + // wasn't able to implement this over there. I sense that this relates to read() being + // the wrong way to read data from USB (like we should be using fgets or something), but + // until I untangle that, this will have to do. + char *received_data = (char*)EM_ASM_INT({ + var len = lengthBytesUTF8(tx) + 1; + var s = _malloc(len); + stringToUTF8(tx, s, len); + return s; + }); + memcpy(line, received_data, min(255, strlen(received_data))); + free(received_data); + EM_ASM({ + tx = ""; + }); +#else + read(0, line, 256); +#endif + if (strlen(line)) filesystem_process_command(line); + } + event.subsecond = 0; return can_sleep && (movement_state.light_ticks == -1) && !movement_state.is_buzzing; diff --git a/movement/watch_faces/sensor/accelerometer_data_acquisition_face.c b/movement/watch_faces/sensor/accelerometer_data_acquisition_face.c index 6ef9c6db..9da8b35c 100644 --- a/movement/watch_faces/sensor/accelerometer_data_acquisition_face.c +++ b/movement/watch_faces/sensor/accelerometer_data_acquisition_face.c @@ -282,7 +282,7 @@ static void update(accelerometer_data_acquisition_state_t *state) { } static void update_settings(accelerometer_data_acquisition_state_t *state) { - char buf[12]; + char buf[13]; watch_clear_colon(); if (state->beep_with_countdown) watch_set_indicator(WATCH_INDICATOR_BELL); else watch_clear_indicator(WATCH_INDICATOR_BELL); diff --git a/watch-library/hardware/linker/saml22j18.ld b/watch-library/hardware/linker/saml22j18.ld index a9801509..211e5346 100755 --- a/watch-library/hardware/linker/saml22j18.ld +++ b/watch-library/hardware/linker/saml22j18.ld @@ -32,11 +32,18 @@ OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) SEARCH_DIR(.) -/* Memory Spaces Definitions */ +/* Memory Space Definitions: + * 0x00000000-0x00002000: Bootloader (length 0x2000 or 8192 bytes) + * 0x00002000-0x0003C000: Firmware (length 0x3A000 or 237568 bytes) + * 0x0003C000-0x00040000: EEPROM Emulation (length 0x2000 or 8192 bytes) + * 0x20000000-0x20008000: RAM (length 0x8000 or 32768 bytes) + */ MEMORY { - rom (rx) : ORIGIN = 0x2000, LENGTH = 0x00040000-0x2000 - ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00008000 + bootloader (rx) : ORIGIN = 0x0, LENGTH = 0x2000 + rom (rx) : ORIGIN = 0x2000, LENGTH = 0x00040000-0x2000-0x2000 + eeprom (r) : ORIGIN = 0x00040000-0x2000, LENGTH = 0x2000 + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00008000 } /* The stack size used by the application. NOTE: you need to adjust according to your application. */ diff --git a/watch-library/hardware/watch/watch.c b/watch-library/hardware/watch/watch.c index 32bbccbb..b3dc4e8d 100644 --- a/watch-library/hardware/watch/watch.c +++ b/watch-library/hardware/watch/watch.c @@ -41,3 +41,7 @@ void SYSTEM_Handler(void) { bool watch_is_buzzer_or_led_enabled(void){ return hri_mclk_get_APBCMASK_TCC0_bit(MCLK); } + +bool watch_is_usb_enabled(void) { + return USB->DEVICE.CTRLA.bit.ENABLE; +} diff --git a/watch-library/hardware/watch/watch_private.c b/watch-library/hardware/watch/watch_private.c index 4b010d4a..cd607b8e 100644 --- a/watch-library/hardware/watch/watch_private.c +++ b/watch-library/hardware/watch/watch_private.c @@ -255,8 +255,15 @@ int _write(int file, char *ptr, int len) { return 0; } -// this method could be overridden to read stuff from the USB console? but no need rn. -int _read(void) { +static char buf[256] = {0}; + +int _read(int file, char *ptr, int len) { + (void)file; + int actual_length = strlen(buf); + if (actual_length) { + memcpy(ptr, buf, min(len, actual_length)); + return actual_length; + } return 0; } @@ -264,8 +271,17 @@ void USB_Handler(void) { tud_int_handler(0); } +static void cdc_task(void) { + if (tud_cdc_n_available(0)) { + tud_cdc_n_read(0, buf, sizeof(buf)); + } else { + memset(buf, 0, 256); + } +} + void TC0_Handler(void) { tud_task(); + cdc_task(); TC0->COUNT8.INTFLAG.reg |= TC_INTFLAG_OVF; } diff --git a/watch-library/hardware/watch/watch_storage.c b/watch-library/hardware/watch/watch_storage.c new file mode 100644 index 00000000..6c87be53 --- /dev/null +++ b/watch-library/hardware/watch/watch_storage.c @@ -0,0 +1,94 @@ +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include "watch_storage.h" + +#define RWWEE_ADDR_START NVMCTRL_RWW_EEPROM_ADDR +#define RWWEE_ADDR_END (NVMCTRL_RWW_EEPROM_ADDR + NVMCTRL_PAGE_SIZE * NVMCTRL_RWWEE_PAGES) +#define NVM_MEMORY ((volatile uint16_t *)FLASH_ADDR) + +static bool _is_valid_address(uint32_t addr, uint32_t size) { + if ((addr < NVMCTRL_RWW_EEPROM_ADDR) || (addr > (NVMCTRL_RWW_EEPROM_ADDR + NVMCTRL_PAGE_SIZE * NVMCTRL_RWWEE_PAGES))) { + return false; + } + if ((addr + size > (NVMCTRL_RWW_EEPROM_ADDR + NVMCTRL_PAGE_SIZE * NVMCTRL_RWWEE_PAGES))) { + return false; + } + + return true; +} + +bool watch_storage_read(uint32_t row, uint32_t offset, uint8_t *buffer, uint32_t size) { + uint32_t address = RWWEE_ADDR_START + row * NVMCTRL_ROW_SIZE + offset; + if (!_is_valid_address(address, size)) return false; + + uint32_t nvm_address = address / 2; + uint32_t i; + uint16_t data; + + watch_storage_sync(); + + if (address % 2) { + data = NVM_MEMORY[nvm_address++]; + buffer[0] = data >> 8; + i = 1; + } else { + i = 0; + } + + while (i < size) { + data = NVM_MEMORY[nvm_address++]; + buffer[i] = (data & 0xFF); + if (i < (size - 1)) { + buffer[i + 1] = (data >> 8); + } + i += 2; + } + return true; +} + +bool watch_storage_write(uint32_t row, uint32_t offset, const uint8_t *buffer, uint32_t size) { + uint32_t address = RWWEE_ADDR_START + row * NVMCTRL_ROW_SIZE + offset; + if (!_is_valid_address(address, size)) return false; + + watch_storage_sync(); + + uint32_t nvm_address = address / 2; + uint16_t i, data; + + hri_nvmctrl_write_CTRLA_reg(NVMCTRL, NVMCTRL_CTRLA_CMD_PBC | NVMCTRL_CTRLA_CMDEX_KEY); + watch_storage_sync(); + + for (i = 0; i < size; i += 2) { + data = buffer[i]; + if (i < NVMCTRL_PAGE_SIZE - 1) { + data |= (buffer[i + 1] << 8); + } + NVM_MEMORY[nvm_address++] = data; + } + hri_nvmctrl_write_ADDR_reg(NVMCTRL, address / 2); + hri_nvmctrl_write_CTRLA_reg(NVMCTRL, NVMCTRL_CTRLA_CMD_RWWEEWP | NVMCTRL_CTRLA_CMDEX_KEY); + + return true; +} + +bool watch_storage_erase(uint32_t row) { + uint32_t address = RWWEE_ADDR_START + row * NVMCTRL_ROW_SIZE; + if (!_is_valid_address(address, NVMCTRL_ROW_SIZE)) return false; + + watch_storage_sync(); + hri_nvmctrl_write_ADDR_reg(NVMCTRL, address / 2); + hri_nvmctrl_write_CTRLA_reg(NVMCTRL, NVMCTRL_CTRLA_CMD_RWWEEER | NVMCTRL_CTRLA_CMDEX_KEY); + + return true; +} + +bool watch_storage_sync(void) { + while (!hri_nvmctrl_get_interrupt_READY_bit(NVMCTRL)) { + // wait for flash to become ready + } + + hri_nvmctrl_clear_STATUS_reg(NVMCTRL, NVMCTRL_STATUS_MASK); + + return true; +} diff --git a/watch-library/shared/watch/watch.h b/watch-library/shared/watch/watch.h index ce85eed3..b94d36fb 100644 --- a/watch-library/shared/watch/watch.h +++ b/watch-library/shared/watch/watch.h @@ -64,6 +64,7 @@ #include "watch_i2c.h" #include "watch_spi.h" #include "watch_uart.h" +#include "watch_storage.h" #include "watch_deepsleep.h" #include "watch_private.h" @@ -75,4 +76,16 @@ */ bool watch_is_buzzer_or_led_enabled(void); +/** @brief Returns true if USB is enabled. + */ +bool watch_is_usb_enabled(void); + +/** @brief Reads up to len bytes from the USB serial. + * @param file ignored, you can pass in 0 + * @param ptr pointer to a buffer of at least len bytes + * @param len the number of bytes you wish to read, max 256. + * @return The number of bytes read, or zero if no bytes were read. + */ +int read(int file, char *ptr, int len); + #endif /* WATCH_H_ */
\ No newline at end of file diff --git a/watch-library/shared/watch/watch_private.h b/watch-library/shared/watch/watch_private.h index 7bb91d1f..9d55bc21 100644 --- a/watch-library/shared/watch/watch_private.h +++ b/watch-library/shared/watch/watch_private.h @@ -44,7 +44,8 @@ void _watch_enable_usb(void); // this function ends up getting called by printf to log stuff to the USB console. int _write(int file, char *ptr, int len); -// this method could be overridden to read stuff from the USB console? but no need rn. -int _read(void); +// i thought this would be called by gets but it doesn't? anyway it does get called by read() +// so that's our mechanism for reading data from the USB serial console. +int _read(int file, char *ptr, int len); #endif diff --git a/watch-library/shared/watch/watch_storage.h b/watch-library/shared/watch/watch_storage.h new file mode 100644 index 00000000..6026cbcf --- /dev/null +++ b/watch-library/shared/watch/watch_storage.h @@ -0,0 +1,92 @@ +/* + * MIT License + * + * Copyright (c) 2020 Joey Castillo + * + * 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 _WATCH_STORAGE_H_INCLUDED +#define _WATCH_STORAGE_H_INCLUDED +////< @file watch_storage.h + +#include "watch.h" + +#ifndef NVMCTRL_ROW_SIZE +#define NVMCTRL_ROW_SIZE 256 +#endif +#ifndef NVMCTRL_PAGE_SIZE +#define NVMCTRL_PAGE_SIZE 64 +#endif +#ifndef NVMCTRL_RWWEE_PAGES +#define NVMCTRL_RWWEE_PAGES 128 +#endif + +/** @addtogroup storage Flash Storage + * @brief This section covers functions related to the SAM L22's 8 kilobyte EEPROM emulation area. + * @details The SAM L22 inside Sensor Watch has a 256 kilobyte Flash memory array that can be + * programmed with whatever data we want. We use most of it to store the bootloader + * and the application code that runs on your wrist. The bootloader region is read-only, + * and the main application area is only writable by the bootloader (when you drag new + * code onto the WATCHBOOT drive). However! there's also a special 8 kilobyte region + * at the end of the Flash memory called the EEPROM Emulation Area. This EEPROM emulation + * area can be written or erased while the main Flash array is being read. This makes it + * super easy to work with, and useful for storing a small amount of non-volatile data that + * persists across reboots, even when power is lost. + * The functions in this section are very basic, and only cover reading and writing data + * in this area. The region is laid out as 32 rows consisting of 4 pages of 64 bytes. + * 32*4*64 = 8192 bytes. The area can be written one page at a time, but it can only be + * erased one row at a time. You can read at arbitrary word-aligned offsets within a row. + * + * ┌──────────────┬──────────────┬──────────────┬──────────────┐ + * Row 0 │ 64 bytes │ 64 bytes │ 64 bytes │ 64 bytes │ + * ├──────────────┼──────────────┼──────────────┼──────────────┤ + * Row 1 │ 64 bytes │ 64 bytes │ 64 bytes │ 64 bytes │ + * ├──────────────┼──────────────┼──────────────┼──────────────┤ + * ... │ │ │ │ │ + * ├──────────────┼──────────────┼──────────────┼──────────────┤ + * Row 31 │ 64 bytes │ 64 bytes │ 64 bytes │ 64 bytes │ + * └──────────────┴──────────────┴──────────────┴──────────────┘ + */ +/// @{ +/** @brief Reads a range of bytes from the storage area. + * @param row The row you want to read. + * @param offset The offset from the beginning of the row. + * @param buffer A buffer of at least `size` bytes. + * @param size The number of bytes you wish to read. + */ +bool watch_storage_read(uint32_t row, uint32_t offset, uint8_t *buffer, uint32_t size); + +/** @brief Writes bytes to a page in the storage area. Note that the row should already be erased before writing. + * @param row The row containing the page you want to write. + * @param offset The offset from the beginning of the row. Must be a multiple of 64. + * @param buffer The buffer containing the bytes you wish to set. + * @param size The number of bytes you wish to write. + */ +bool watch_storage_write(uint32_t row, uint32_t offset, const uint8_t *buffer, uint32_t size); + +/** @brief Erases a row in the storage area, setting all its bytes to 0xFF. + * @param row The row you want to erase. + */ +bool watch_storage_erase(uint32_t row); + +/** @brief Waits for any pending writes to complete. + */ +bool watch_storage_sync(void); +/// @} +#endif diff --git a/watch-library/simulator/main.c b/watch-library/simulator/main.c index 14bf44e6..ac9db6ac 100644 --- a/watch-library/simulator/main.c +++ b/watch-library/simulator/main.c @@ -102,8 +102,6 @@ static void main_loop_set_sleeping(bool sleeping) { } int main(void) { - printf("Hello, world!\n"); - app_init(); _watch_init(); app_setup(); diff --git a/watch-library/simulator/shell.html b/watch-library/simulator/shell.html index 2f560aba..80e1e2ea 100644 --- a/watch-library/simulator/shell.html +++ b/watch-library/simulator/shell.html @@ -323,6 +323,9 @@ <button onclick="getLocation()">Set location register (will prompt for access)</button> <br> +<input id="input" style="width: 500px"></input> +<button id="submit" onclick="sendText()">Send</button> +<br> <textarea id="output" rows="8" style="width: 100%"></textarea> <script type='text/javascript'> @@ -365,10 +368,16 @@ }; lat = 0; lon = 0; + tx = ""; function updateLocation(location) { lat = Math.round(location.coords.latitude * 100); lon = Math.round(location.coords.longitude * 100); } + function sendText() { + var inputElement = document.getElementById('input'); + tx = inputElement.value + "\n"; + inputElement.value = ""; + } function showError(error) { switch(error.code) { case error.PERMISSION_DENIED: diff --git a/watch-library/simulator/watch/watch.c b/watch-library/simulator/watch/watch.c index 1c965aad..749651af 100644 --- a/watch-library/simulator/watch/watch.c +++ b/watch-library/simulator/watch/watch.c @@ -3,3 +3,7 @@ bool watch_is_buzzer_or_led_enabled(void) { return false; } + +bool watch_is_usb_enabled(void) { + return true; +} diff --git a/watch-library/simulator/watch/watch_private.c b/watch-library/simulator/watch/watch_private.c index 4ddc2182..3425341a 100644 --- a/watch-library/simulator/watch/watch_private.c +++ b/watch-library/simulator/watch/watch_private.c @@ -63,7 +63,7 @@ int _write(int file, char *ptr, int len) { return 0; } -// this method could be overridden to read stuff from the USB console? but no need rn. -int _read(void) { +int _read(int file, char *ptr, int len) { + // TODO: hook to UI return 0; } diff --git a/watch-library/simulator/watch/watch_storage.c b/watch-library/simulator/watch/watch_storage.c new file mode 100644 index 00000000..27011807 --- /dev/null +++ b/watch-library/simulator/watch/watch_storage.c @@ -0,0 +1,32 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "watch_storage.h" + +uint8_t storage[NVMCTRL_ROW_SIZE * NVMCTRL_RWWEE_PAGES]; + +bool watch_storage_read(uint32_t row, uint32_t offset, uint8_t *buffer, uint32_t size) { + // printf("read row %ld offset %ld size %ld\n", row, offset, size); + memcpy(buffer, storage + row * NVMCTRL_ROW_SIZE + offset, size); + + return true; +} + +bool watch_storage_write(uint32_t row, uint32_t offset, const uint8_t *buffer, uint32_t size) { + // printf("write row %ld offset %ld size %ld\n", row, offset, size); + memcpy(storage + row * NVMCTRL_ROW_SIZE + offset, buffer, size); + + return true; +} + +bool watch_storage_erase(uint32_t row) { + // printf("erase row %ld\n", row); + memset(storage + row * NVMCTRL_ROW_SIZE, 0xff, NVMCTRL_ROW_SIZE); + + return true; +} + +bool watch_storage_sync(void) { + // nothing to do here! + return true; +} |