diff options
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; +}  | 
