From 266be307770abe11c220eb493388807920914b7a Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 25 Aug 2017 13:00:27 -0700 Subject: [PATCH] atmel-samd: Introduce a nvm module for non-volatile byte-level memory access. (#203) * atmel-samd: Introduce a nvm module for non-volatile byte-level memory access. This allows for persisting small configuration values even when the file system is read-only from CircuitPython. Fixes #160 * Review feedback: * Add tests. * Fix non-zero index. * Fix len() --- atmel-samd/Makefile | 2 + .../boards/arduino_zero/mpconfigboard.h | 2 + .../circuitplayground_express/mpconfigboard.h | 6 +- .../feather_m0_adalogger/mpconfigboard.h | 2 + .../boards/feather_m0_basic/mpconfigboard.h | 2 + .../boards/feather_m0_express/mpconfigboard.h | 6 +- atmel-samd/boards/gemma_m0/mpconfigboard.h | 2 + .../boards/metro_m0_express/mpconfigboard.h | 6 +- .../samd21x18-bootloader-crystalless.ld | 4 +- ...8-bootloader-external-flash-crystalless.ld | 3 +- .../samd21x18-bootloader-external-flash.ld | 2 +- atmel-samd/boards/samd21x18-bootloader.ld | 2 +- atmel-samd/boards/samd21x18-external-flash.ld | 3 +- atmel-samd/boards/samd21x18.ld | 3 +- atmel-samd/boards/trinket_m0/mpconfigboard.h | 2 + .../common-hal/microcontroller/__init__.c | 14 ++ atmel-samd/common-hal/nvm/ByteArray.c | 88 ++++++++++ atmel-samd/common-hal/nvm/ByteArray.h | 38 +++++ atmel-samd/common-hal/nvm/__init__.c | 1 + atmel-samd/internal_flash.c | 7 - atmel-samd/internal_flash.h | 6 + atmel-samd/main.c | 43 ++++- atmel-samd/mpconfigport.h | 2 + shared-bindings/microcontroller/__init__.c | 13 +- shared-bindings/microcontroller/__init__.h | 8 + shared-bindings/nvm/ByteArray.c | 159 ++++++++++++++++++ shared-bindings/nvm/ByteArray.h | 43 +++++ shared-bindings/nvm/__init__.c | 62 +++++++ shared-bindings/nvm/__init__.h | 30 ++++ tests/circuitpython/nvm_not_present.py | 6 + tests/circuitpython/nvm_present.py | 22 +++ tests/skip_if.py | 11 ++ 32 files changed, 575 insertions(+), 25 deletions(-) create mode 100644 atmel-samd/common-hal/nvm/ByteArray.c create mode 100644 atmel-samd/common-hal/nvm/ByteArray.h create mode 100644 atmel-samd/common-hal/nvm/__init__.c create mode 100644 shared-bindings/nvm/ByteArray.c create mode 100644 shared-bindings/nvm/ByteArray.h create mode 100644 shared-bindings/nvm/__init__.c create mode 100644 shared-bindings/nvm/__init__.h create mode 100644 tests/circuitpython/nvm_not_present.py create mode 100644 tests/circuitpython/nvm_present.py diff --git a/atmel-samd/Makefile b/atmel-samd/Makefile index 913461261a..5476a91e9d 100644 --- a/atmel-samd/Makefile +++ b/atmel-samd/Makefile @@ -252,6 +252,8 @@ SRC_COMMON_HAL = \ microcontroller/__init__.c \ microcontroller/Pin.c \ neopixel_write/__init__.c \ + nvm/__init__.c \ + nvm/ByteArray.c \ os/__init__.c \ pulseio/__init__.c \ pulseio/PulseIn.c \ diff --git a/atmel-samd/boards/arduino_zero/mpconfigboard.h b/atmel-samd/boards/arduino_zero/mpconfigboard.h index dd4ec88d33..1499638402 100644 --- a/atmel-samd/boards/arduino_zero/mpconfigboard.h +++ b/atmel-samd/boards/arduino_zero/mpconfigboard.h @@ -12,4 +12,6 @@ #include "internal_flash.h" +#define CIRCUITPY_INTERNAL_NVM_SIZE 0 + #define BOARD_FLASH_SIZE (0x00040000 - 0x2000 - 0x010000) diff --git a/atmel-samd/boards/circuitplayground_express/mpconfigboard.h b/atmel-samd/boards/circuitplayground_express/mpconfigboard.h index 9499d25a28..f9ff05084a 100644 --- a/atmel-samd/boards/circuitplayground_express/mpconfigboard.h +++ b/atmel-samd/boards/circuitplayground_express/mpconfigboard.h @@ -25,7 +25,11 @@ #include "spi_flash.h" -#define BOARD_FLASH_SIZE (0x00040000 - 0x2000) +// If you change this, then make sure to update the linker scripts as well to +// make sure you don't overwrite code. +#define CIRCUITPY_INTERNAL_NVM_SIZE 256 + +#define BOARD_FLASH_SIZE (0x00040000 - 0x2000 - CIRCUITPY_INTERNAL_NVM_SIZE) #include "flash_S25FL216K.h" #include "flash_GD25Q16C.h" diff --git a/atmel-samd/boards/feather_m0_adalogger/mpconfigboard.h b/atmel-samd/boards/feather_m0_adalogger/mpconfigboard.h index f1b9dfcbe0..2a2d21dd3d 100644 --- a/atmel-samd/boards/feather_m0_adalogger/mpconfigboard.h +++ b/atmel-samd/boards/feather_m0_adalogger/mpconfigboard.h @@ -11,4 +11,6 @@ #include "internal_flash.h" +#define CIRCUITPY_INTERNAL_NVM_SIZE 0 + #define BOARD_FLASH_SIZE (0x00040000 - 0x2000 - 0x010000) diff --git a/atmel-samd/boards/feather_m0_basic/mpconfigboard.h b/atmel-samd/boards/feather_m0_basic/mpconfigboard.h index ff6088c3a3..a8a8247fa4 100644 --- a/atmel-samd/boards/feather_m0_basic/mpconfigboard.h +++ b/atmel-samd/boards/feather_m0_basic/mpconfigboard.h @@ -11,4 +11,6 @@ #include "internal_flash.h" +#define CIRCUITPY_INTERNAL_NVM_SIZE 0 + #define BOARD_FLASH_SIZE (0x00040000 - 0x2000 - 0x010000) diff --git a/atmel-samd/boards/feather_m0_express/mpconfigboard.h b/atmel-samd/boards/feather_m0_express/mpconfigboard.h index 929a8c0f30..896a04dd83 100644 --- a/atmel-samd/boards/feather_m0_express/mpconfigboard.h +++ b/atmel-samd/boards/feather_m0_express/mpconfigboard.h @@ -23,7 +23,11 @@ #include "spi_flash.h" -#define BOARD_FLASH_SIZE (0x00040000 - 0x2000) +// If you change this, then make sure to update the linker scripts as well to +// make sure you don't overwrite code. +#define CIRCUITPY_INTERNAL_NVM_SIZE 256 + +#define BOARD_FLASH_SIZE (0x00040000 - 0x2000 - CIRCUITPY_INTERNAL_NVM_SIZE) #include "flash_S25FL216K.h" #include "flash_GD25Q16C.h" diff --git a/atmel-samd/boards/gemma_m0/mpconfigboard.h b/atmel-samd/boards/gemma_m0/mpconfigboard.h index ae1aa24a1e..8988c9a630 100644 --- a/atmel-samd/boards/gemma_m0/mpconfigboard.h +++ b/atmel-samd/boards/gemma_m0/mpconfigboard.h @@ -11,6 +11,8 @@ #define MICROPY_PORT_A (PORT_PA00 | PORT_PA01 | PORT_PA24 | PORT_PA25) #define MICROPY_PORT_B (0) +#define CIRCUITPY_INTERNAL_NVM_SIZE 0 + #include "internal_flash.h" #define BOARD_FLASH_SIZE (0x00040000 - 0x2000 - 0x010000) diff --git a/atmel-samd/boards/metro_m0_express/mpconfigboard.h b/atmel-samd/boards/metro_m0_express/mpconfigboard.h index 9bb9a2f79f..cbc28dc781 100644 --- a/atmel-samd/boards/metro_m0_express/mpconfigboard.h +++ b/atmel-samd/boards/metro_m0_express/mpconfigboard.h @@ -25,7 +25,11 @@ #include "spi_flash.h" -#define BOARD_FLASH_SIZE (0x00040000 - 0x2000) +// If you change this, then make sure to update the linker scripts as well to +// make sure you don't overwrite code. +#define CIRCUITPY_INTERNAL_NVM_SIZE 256 + +#define BOARD_FLASH_SIZE (0x00040000 - 0x2000 - CIRCUITPY_INTERNAL_NVM_SIZE) #include "flash_S25FL216K.h" #include "flash_GD25Q16C.h" diff --git a/atmel-samd/boards/samd21x18-bootloader-crystalless.ld b/atmel-samd/boards/samd21x18-bootloader-crystalless.ld index 96527ab7e5..56ae5feb46 100644 --- a/atmel-samd/boards/samd21x18-bootloader-crystalless.ld +++ b/atmel-samd/boards/samd21x18-bootloader-crystalless.ld @@ -5,8 +5,8 @@ /* Specify the memory areas */ MEMORY { - /* Leave 8KiB for the bootloader, 256b for persistent config (clock), and 64k for the flash file system. */ - FLASH (rx) : ORIGIN = 0x00000000+0x2000, LENGTH = 0x00040000 - 0x2000 - 0x100 - 0x010000 + /* Leave 8KiB for the bootloader, 256b for persistent config (clock), 64k for the flash file system and 256b for the user config. */ + FLASH (rx) : ORIGIN = 0x00000000+0x2000, LENGTH = 0x00040000 - 0x2000 - 0x100 - 0x010000 - 0x100 RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x008000 /* 32 KiB */ } diff --git a/atmel-samd/boards/samd21x18-bootloader-external-flash-crystalless.ld b/atmel-samd/boards/samd21x18-bootloader-external-flash-crystalless.ld index a6be02f90f..1c04247473 100644 --- a/atmel-samd/boards/samd21x18-bootloader-external-flash-crystalless.ld +++ b/atmel-samd/boards/samd21x18-bootloader-external-flash-crystalless.ld @@ -5,7 +5,8 @@ /* Specify the memory areas */ MEMORY { - FLASH (rx) : ORIGIN = 0x00000000 + 0x2000, LENGTH = 0x00040000 - 0x2000 - 0x100 /* Leave 8KiB for the bootloader and 256b for config. */ + /* Leave 8KiB for the bootloader, 256b for internal config and 256b for user config. */ + FLASH (rx) : ORIGIN = 0x00000000 + 0x2000, LENGTH = 0x00040000 - 0x2000 - 0x100 - 0x100 RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x008000 /* 32 KiB */ } diff --git a/atmel-samd/boards/samd21x18-bootloader-external-flash.ld b/atmel-samd/boards/samd21x18-bootloader-external-flash.ld index bb1d3fdc44..4346c170ce 100644 --- a/atmel-samd/boards/samd21x18-bootloader-external-flash.ld +++ b/atmel-samd/boards/samd21x18-bootloader-external-flash.ld @@ -5,7 +5,7 @@ /* Specify the memory areas */ MEMORY { - FLASH (rx) : ORIGIN = 0x00000000 + 0x2000, LENGTH = 0x00040000 - 0x2000 /* Leave 8KiB for the bootloader. */ + FLASH (rx) : ORIGIN = 0x00000000 + 0x2000, LENGTH = 0x00040000 - 0x2000 - 0x100 /* Leave 8KiB for the bootloader and 256b for user config. */ RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x008000 /* 32 KiB */ } diff --git a/atmel-samd/boards/samd21x18-bootloader.ld b/atmel-samd/boards/samd21x18-bootloader.ld index 0ea475ede1..13a3f75c18 100644 --- a/atmel-samd/boards/samd21x18-bootloader.ld +++ b/atmel-samd/boards/samd21x18-bootloader.ld @@ -5,7 +5,7 @@ /* Specify the memory areas */ MEMORY { - /* Leave 8KiB for the bootloader and 64k for the flash file system. */ + /* Leave 8KiB for the bootloader, and 64k for the flash file system. */ FLASH (rx) : ORIGIN = 0x00000000+0x2000, LENGTH = 0x00040000 - 0x2000 - 0x010000 RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x008000 /* 32 KiB */ } diff --git a/atmel-samd/boards/samd21x18-external-flash.ld b/atmel-samd/boards/samd21x18-external-flash.ld index 90cb6160a7..f5797cf6f5 100644 --- a/atmel-samd/boards/samd21x18-external-flash.ld +++ b/atmel-samd/boards/samd21x18-external-flash.ld @@ -5,7 +5,8 @@ /* Specify the memory areas */ MEMORY { - FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00040000 - 0x100 /* 256 KiB but leave 256b for config */ + /* 256 KiB but leave 256b for internal config and 256b for user config (protected eeprom) */ + FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00040000 - 0x100 - 0x100 RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x008000 /* 32 KiB */ } diff --git a/atmel-samd/boards/samd21x18.ld b/atmel-samd/boards/samd21x18.ld index 98a24a8110..a68094758f 100644 --- a/atmel-samd/boards/samd21x18.ld +++ b/atmel-samd/boards/samd21x18.ld @@ -5,7 +5,8 @@ /* Specify the memory areas */ MEMORY { - FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00040000 - 0x100 - 0x010000 /* Leave 256b for config and 64k for the flash file system. */ + /* Leave 256b for internal config, 64k for the flash file system and 256b for user config. */ + FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00040000 - 0x100 - 0x010000 RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x008000 /* 32 KiB */ } diff --git a/atmel-samd/boards/trinket_m0/mpconfigboard.h b/atmel-samd/boards/trinket_m0/mpconfigboard.h index 7bb4f14cf1..8fc70f22d5 100644 --- a/atmel-samd/boards/trinket_m0/mpconfigboard.h +++ b/atmel-samd/boards/trinket_m0/mpconfigboard.h @@ -12,4 +12,6 @@ #include "internal_flash.h" +#define CIRCUITPY_INTERNAL_NVM_SIZE 0 + #define BOARD_FLASH_SIZE (0x00040000 - 0x2000 - 0x010000) diff --git a/atmel-samd/common-hal/microcontroller/__init__.c b/atmel-samd/common-hal/microcontroller/__init__.c index 12a530f686..50e8cdcef6 100644 --- a/atmel-samd/common-hal/microcontroller/__init__.c +++ b/atmel-samd/common-hal/microcontroller/__init__.c @@ -25,9 +25,12 @@ */ #include "py/mphal.h" +#include "py/obj.h" #include "samd21_pins.h" +#include "shared-bindings/nvm/ByteArray.h" + void common_hal_mcu_delay_us(uint32_t delay) { mp_hal_delay_us(delay); } @@ -47,6 +50,17 @@ void common_hal_mcu_enable_interrupts(void) { cpu_irq_restore(irq_flags); } +// NVM is only available on Express boards for now. +#if CIRCUITPY_INTERNAL_NVM_SIZE > 0 +nvm_bytearray_obj_t common_hal_mcu_nvm_obj = { + .base = { + .type = &nvm_bytearray_type, + }, + .len = NVMCTRL_ROW_SIZE, + .start_address = (uint8_t*) (FLASH_SIZE - NVMCTRL_ROW_SIZE) +}; +#endif + // This maps MCU pin names to pin objects. STATIC const mp_map_elem_t mcu_pin_global_dict_table[] = { // Pins in datasheet order. diff --git a/atmel-samd/common-hal/nvm/ByteArray.c b/atmel-samd/common-hal/nvm/ByteArray.c new file mode 100644 index 0000000000..165634b83c --- /dev/null +++ b/atmel-samd/common-hal/nvm/ByteArray.c @@ -0,0 +1,88 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Scott Shawcroft 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 "common-hal/nvm/ByteArray.h" + +#include "asf/sam0/drivers/nvm/nvm.h" + +#include +#include + +uint32_t common_hal_nvm_bytearray_get_length(nvm_bytearray_obj_t *self) { + return self->len; +} + +bool common_hal_nvm_bytearray_set_bytes(nvm_bytearray_obj_t *self, + uint32_t start_index, uint8_t* values, uint32_t len) { + uint32_t total_written = 0; + for (uint32_t i = 0; i < self->len / NVMCTRL_ROW_SIZE; i++) { + uint32_t row_start = NVMCTRL_ROW_SIZE * i; + if (row_start + NVMCTRL_ROW_SIZE < start_index || start_index + len < row_start) { + continue; + } + uint8_t temp_row[NVMCTRL_ROW_SIZE]; + memcpy(temp_row, + self->start_address + row_start, + NVMCTRL_ROW_SIZE); + enum status_code error_code; + do { + error_code = nvm_erase_row((uint32_t) self->start_address + row_start); + } while (error_code == STATUS_BUSY); + if (error_code != STATUS_OK) { + return false; + } + uint32_t data_start = 0; + if (start_index > row_start) { + data_start = start_index - row_start; + } + uint32_t data_len = len; + uint32_t data_remaining = data_len - total_written; + uint32_t row_remaining = NVMCTRL_ROW_SIZE - data_start; + if (data_remaining > row_remaining) { + data_len = row_remaining; + } + memcpy(temp_row + data_start, + values + total_written, + data_len); + for (int page = 0; page < NVMCTRL_ROW_SIZE / NVMCTRL_PAGE_SIZE; page++) { + do { + error_code = nvm_write_buffer((uint32_t) self->start_address + row_start + page * NVMCTRL_PAGE_SIZE, + temp_row + page * NVMCTRL_PAGE_SIZE, + NVMCTRL_PAGE_SIZE); + } while (error_code == STATUS_BUSY); + if (error_code != STATUS_OK) { + return false; + } + } + } + return true; +} + +// NVM memory is memory mapped so reading it is easy. +void common_hal_nvm_bytearray_get_bytes(nvm_bytearray_obj_t *self, + uint32_t start_index, uint32_t len, uint8_t* values) { + memcpy(values, self->start_address + start_index, len); +} diff --git a/atmel-samd/common-hal/nvm/ByteArray.h b/atmel-samd/common-hal/nvm/ByteArray.h new file mode 100644 index 0000000000..cd23c894dd --- /dev/null +++ b/atmel-samd/common-hal/nvm/ByteArray.h @@ -0,0 +1,38 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Scott Shawcroft 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 __MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_NVM_BYTEARRAY_H__ +#define __MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_NVM_BYTEARRAY_H__ + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + uint8_t* start_address; + uint32_t len; +} nvm_bytearray_obj_t; + +#endif // __MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_NVM_BYTEARRAY_H__ diff --git a/atmel-samd/common-hal/nvm/__init__.c b/atmel-samd/common-hal/nvm/__init__.c new file mode 100644 index 0000000000..1b702a1584 --- /dev/null +++ b/atmel-samd/common-hal/nvm/__init__.c @@ -0,0 +1 @@ +// No nvm module functions. diff --git a/atmel-samd/internal_flash.c b/atmel-samd/internal_flash.c index 1ff1cabcf6..23b1153b3a 100644 --- a/atmel-samd/internal_flash.c +++ b/atmel-samd/internal_flash.c @@ -40,12 +40,6 @@ #include "rgb_led_status.h" -#define TOTAL_INTERNAL_FLASH_SIZE 0x010000 - -#define INTERNAL_FLASH_MEM_SEG1_START_ADDR (0x00040000 - TOTAL_INTERNAL_FLASH_SIZE) -#define INTERNAL_FLASH_PART1_START_BLOCK (0x1) -#define INTERNAL_FLASH_PART1_NUM_BLOCKS (TOTAL_INTERNAL_FLASH_SIZE / FILESYSTEM_BLOCK_SIZE) - void internal_flash_init(void) { // Activity LED for flash writes. #ifdef MICROPY_HW_LED_MSC @@ -120,7 +114,6 @@ static int32_t convert_block_to_flash_addr(uint32_t block) { } bool internal_flash_read_block(uint8_t *dest, uint32_t block) { - //printf("RD %u\n", block); if (block == 0) { // fake the MBR so we can decide on our own partition table diff --git a/atmel-samd/internal_flash.h b/atmel-samd/internal_flash.h index 52ae4c9f1b..e8b4d7ae0e 100644 --- a/atmel-samd/internal_flash.h +++ b/atmel-samd/internal_flash.h @@ -32,6 +32,12 @@ #define FLASH_ROOT_POINTERS +#define TOTAL_INTERNAL_FLASH_SIZE 0x010000 + +#define INTERNAL_FLASH_MEM_SEG1_START_ADDR (0x00040000 - TOTAL_INTERNAL_FLASH_SIZE - CIRCUITPY_INTERNAL_NVM_SIZE) +#define INTERNAL_FLASH_PART1_START_BLOCK (0x1) +#define INTERNAL_FLASH_PART1_NUM_BLOCKS (TOTAL_INTERNAL_FLASH_SIZE / FILESYSTEM_BLOCK_SIZE) + #define INTERNAL_FLASH_SYSTICK_MASK (0x1ff) // 512ms #define INTERNAL_FLASH_IDLE_TICK(tick) (((tick) & INTERNAL_FLASH_SYSTICK_MASK) == 2) diff --git a/atmel-samd/main.c b/atmel-samd/main.c index b3cfa69fdd..76a98dc606 100644 --- a/atmel-samd/main.c +++ b/atmel-samd/main.c @@ -34,13 +34,6 @@ #include "common-hal/pulseio/PWMOut.h" #include "common-hal/usb_hid/__init__.h" -#ifdef EXPRESS_BOARD -#include "common-hal/touchio/TouchIn.h" -#define INTERNAL_CIRCUITPY_CONFIG_START_ADDR (0x00040000 - 0x100) -#else -#define INTERNAL_CIRCUITPY_CONFIG_START_ADDR (0x00040000 - 0x010000 - 0x100) -#endif - #include "autoreload.h" #include "flash_api.h" #include "mpconfigboard.h" @@ -48,6 +41,13 @@ #include "shared_dma.h" #include "tick.h" +#ifdef EXPRESS_BOARD +#include "common-hal/touchio/TouchIn.h" +#define INTERNAL_CIRCUITPY_CONFIG_START_ADDR (0x00040000 - 0x100 - CIRCUITPY_INTERNAL_NVM_SIZE) +#else +#define INTERNAL_CIRCUITPY_CONFIG_START_ADDR (0x00040000 - 0x010000 - 0x100 - CIRCUITPY_INTERNAL_NVM_SIZE) +#endif + fs_user_mount_t fs_user_mount_flash; mp_vfs_mount_t mp_vfs_mount_flash; @@ -568,6 +568,35 @@ safe_mode_t samd21_init(void) { return USER_SAFE_MODE; } + #if CIRCUITPY_INTERNAL_NVM_SIZE > 0 + // Upgrade the nvm flash to include one sector for eeprom emulation. + struct nvm_fusebits fuses; + if (nvm_get_fuses(&fuses) == STATUS_OK && + fuses.eeprom_size == NVM_EEPROM_EMULATOR_SIZE_0) { + #ifdef INTERNAL_FLASH_FS + // Shift the internal file system up one row. + for (uint8_t row = 0; row < TOTAL_INTERNAL_FLASH_SIZE / NVMCTRL_ROW_SIZE; row++) { + uint32_t new_row_address = INTERNAL_FLASH_MEM_SEG1_START_ADDR + row * NVMCTRL_ROW_SIZE; + nvm_erase_row(new_row_address); + nvm_write_buffer(new_row_address, + (uint8_t*) (new_row_address + CIRCUITPY_INTERNAL_EEPROM_SIZE), + NVMCTRL_ROW_SIZE); + } + #endif + uint32_t nvm_size = CIRCUITPY_INTERNAL_NVM_SIZE; + uint8_t enum_value = 6; + while (nvm_size > 256 && enum_value != 255) { + nvm_size /= 2; + enum_value -= 1; + } + if (enum_value != 255 && nvm_size == 256) { + // Mark the last section as eeprom now. + fuses.eeprom_size = (enum nvm_eeprom_emulator_size) enum_value; + nvm_set_fuses(&fuses); + } + } + #endif + return NO_SAFE_MODE; } diff --git a/atmel-samd/mpconfigport.h b/atmel-samd/mpconfigport.h index 0059ea9769..ba6b54d498 100644 --- a/atmel-samd/mpconfigport.h +++ b/atmel-samd/mpconfigport.h @@ -147,6 +147,7 @@ extern const struct _mp_obj_module_t os_module; extern const struct _mp_obj_module_t random_module; extern const struct _mp_obj_module_t storage_module; extern const struct _mp_obj_module_t time_module; +extern const struct _mp_obj_module_t cpy_nvm_module; extern const struct _mp_obj_module_t neopixel_write_module; extern const struct _mp_obj_module_t uheap_module; extern const struct _mp_obj_module_t ustack_module; @@ -174,6 +175,7 @@ extern const struct _mp_obj_module_t usb_hid_module; #define EXTRA_BUILTIN_MODULES \ { MP_OBJ_NEW_QSTR(MP_QSTR_audioio), (mp_obj_t)&audioio_module }, \ { MP_OBJ_NEW_QSTR(MP_QSTR_audiobusio), (mp_obj_t)&audiobusio_module }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_nvm), (mp_obj_t)&cpy_nvm_module }, \ { MP_OBJ_NEW_QSTR(MP_QSTR_pulseio), (mp_obj_t)&pulseio_module }, \ { MP_OBJ_NEW_QSTR(MP_QSTR_bitbangio), (mp_obj_t)&bitbangio_module } #define EXPRESS_BOARD diff --git a/shared-bindings/microcontroller/__init__.c b/shared-bindings/microcontroller/__init__.c index fbe359ed94..56d363eed0 100644 --- a/shared-bindings/microcontroller/__init__.c +++ b/shared-bindings/microcontroller/__init__.c @@ -90,6 +90,12 @@ STATIC mp_obj_t mcu_enable_interrupts(void) { } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mcu_enable_interrupts_obj, mcu_enable_interrupts); +//| .. attribute:: nvm +//| +//| Available non-volatile memory. Its a `nvm.ByteArray` when available or +//| ``None`` otherwise. +//| + //| :mod:`microcontroller.pin` --- Microcontroller pin names //| -------------------------------------------------------- //| @@ -109,7 +115,12 @@ STATIC const mp_rom_map_elem_t mcu_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_delay_us), MP_ROM_PTR(&mcu_delay_us_obj) }, { MP_ROM_QSTR(MP_QSTR_disable_interrupts), MP_ROM_PTR(&mcu_disable_interrupts_obj) }, { MP_ROM_QSTR(MP_QSTR_enable_interrupts), MP_ROM_PTR(&mcu_enable_interrupts_obj) }, - { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&mcu_pin_type) }, + #if CIRCUITPY_INTERNAL_NVM_SIZE > 0 + { MP_ROM_QSTR(MP_QSTR_nvm), &common_hal_mcu_nvm_obj }, + #else + { MP_ROM_QSTR(MP_QSTR_nvm), &mp_const_none_obj }, + #endif + { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&mcu_pin_type) }, { MP_ROM_QSTR(MP_QSTR_pin), MP_ROM_PTR(&mcu_pin_module) }, }; diff --git a/shared-bindings/microcontroller/__init__.h b/shared-bindings/microcontroller/__init__.h index 8c0b071ecc..52aabf9b87 100644 --- a/shared-bindings/microcontroller/__init__.h +++ b/shared-bindings/microcontroller/__init__.h @@ -27,6 +27,7 @@ #ifndef __MICROPY_INCLUDED_SHARED_BINDINGS_MICROCONTROLLER___INIT___H__ #define __MICROPY_INCLUDED_SHARED_BINDINGS_MICROCONTROLLER___INIT___H__ +#include "py/mpconfig.h" #include "py/obj.h" extern void common_hal_mcu_delay_us(uint32_t); @@ -36,4 +37,11 @@ extern void common_hal_mcu_enable_interrupts(void); extern const mp_obj_dict_t mcu_pin_globals; +#if CIRCUITPY_INTERNAL_NVM_SIZE > 0 + +#include "common-hal/nvm/ByteArray.h" +extern const nvm_bytearray_obj_t common_hal_mcu_nvm_obj; + +#endif + #endif // __MICROPY_INCLUDED_SHARED_BINDINGS_MICROCONTROLLER___INIT___H__ diff --git a/shared-bindings/nvm/ByteArray.c b/shared-bindings/nvm/ByteArray.c new file mode 100644 index 0000000000..0ba511a779 --- /dev/null +++ b/shared-bindings/nvm/ByteArray.c @@ -0,0 +1,159 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Scott Shawcroft 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 "py/binary.h" +#include "py/objproperty.h" +#include "py/runtime.h" +#include "py/runtime0.h" +#include "shared-bindings/nvm/ByteArray.h" + +//| .. currentmodule:: nvm +//| +//| :class:`ByteArray` -- Presents a stretch of non-volatile memory as a bytearray. +//| ================================================================================ +//| +//| Non-volatile memory is available as a byte array that persists over reloads +//| and power cycles. +//| +//| Usage:: +//| +//| import microcontroller +//| microcontroller.nvm[0] = 0xcc +//| + +//| .. class:: ByteArray() +//| +//| Not currently dynamically supported. Access one through `microcontroller.nvm`. +//| +STATIC mp_obj_t nvm_bytearray_make_new(const mp_obj_type_t *type, + mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) { + return mp_const_none; +} + +//| .. method:: __len__() +//| +//| Return the length. This is used by (`len`) +//| +STATIC mp_obj_t nvm_bytearray_unary_op(mp_uint_t op, mp_obj_t self_in) { + nvm_bytearray_obj_t *self = MP_OBJ_TO_PTR(self_in); + uint16_t len = common_hal_nvm_bytearray_get_length(self); + switch (op) { + case MP_UNARY_OP_BOOL: return mp_obj_new_bool(len != 0); + case MP_UNARY_OP_LEN: return MP_OBJ_NEW_SMALL_INT(len); + default: return MP_OBJ_NULL; // op not supported + } +} + +STATIC const mp_rom_map_elem_t nvm_bytearray_locals_dict_table[] = { +}; + +STATIC MP_DEFINE_CONST_DICT(nvm_bytearray_locals_dict, nvm_bytearray_locals_dict_table); + +STATIC mp_obj_t nvm_bytearray_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value) { + if (value == MP_OBJ_NULL) { + // delete item + // slice deletion + return MP_OBJ_NULL; // op not supported + } else { + nvm_bytearray_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (0) { +#if MICROPY_PY_BUILTINS_SLICE + } else if (MP_OBJ_IS_TYPE(index_in, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(common_hal_nvm_bytearray_get_length(self), index_in, &slice)) { + mp_raise_NotImplementedError("only slices with step=1 (aka None) are supported"); + } + if (value != MP_OBJ_SENTINEL) { + #if MICROPY_PY_ARRAY_SLICE_ASSIGN + // Assign + size_t src_len = slice.stop - slice.start; + uint8_t* src_items; + if (MP_OBJ_IS_TYPE(value, &mp_type_array) || + MP_OBJ_IS_TYPE(value, &mp_type_bytearray) || + MP_OBJ_IS_TYPE(value, &mp_type_memoryview) || + MP_OBJ_IS_TYPE(value, &mp_type_bytes)) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(value, &bufinfo, MP_BUFFER_READ); + if (bufinfo.len != src_len) { + mp_raise_ValueError("Slice and value different lengths."); + } + src_len = bufinfo.len; + src_items = bufinfo.buf; + if (1 != mp_binary_get_size('@', bufinfo.typecode, NULL)) { + mp_raise_ValueError("Array values should be single bytes."); + } + } else { + mp_raise_NotImplementedError("array/bytes required on right side"); + } + + if (!common_hal_nvm_bytearray_set_bytes(self, slice.start, src_items, src_len)) { + mp_raise_RuntimeError("Unable to write to nvm."); + } + return mp_const_none; + #else + return MP_OBJ_NULL; // op not supported + #endif + } else { + // Read slice. + size_t len = slice.stop - slice.start; + uint8_t *items = m_new(uint8_t, len); + common_hal_nvm_bytearray_get_bytes(self, slice.start, len, items); + return mp_obj_new_bytearray_by_ref(len, items); + } +#endif + } else { + // Single index rather than slice. + size_t index = mp_get_index(self->base.type, self->len, index_in, false); + if (value == MP_OBJ_SENTINEL) { + // load + uint8_t value_out; + common_hal_nvm_bytearray_get_bytes(self, index, 1, &value_out); + return MP_OBJ_NEW_SMALL_INT(value_out); + } else { + // store + mp_int_t byte_value = mp_obj_get_int(value); + if (byte_value > 0xff || byte_value < 0) { + mp_raise_ValueError("Bytes must be between 0 and 255."); + } + uint8_t short_value = byte_value; + if (!common_hal_nvm_bytearray_set_bytes(self, index, &short_value, 1)) { + mp_raise_RuntimeError("Unable to write to nvm."); + } + return mp_const_none; + } + } + } +} + +const mp_obj_type_t nvm_bytearray_type = { + { &mp_type_type }, + .name = MP_QSTR_ByteArray, + .make_new = nvm_bytearray_make_new, + .subscr = nvm_bytearray_subscr, + .unary_op = nvm_bytearray_unary_op, + .print = NULL, + .locals_dict = (mp_obj_t)&nvm_bytearray_locals_dict, +}; diff --git a/shared-bindings/nvm/ByteArray.h b/shared-bindings/nvm/ByteArray.h new file mode 100644 index 0000000000..43d942c230 --- /dev/null +++ b/shared-bindings/nvm/ByteArray.h @@ -0,0 +1,43 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Scott Shawcroft 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 __MICROPY_INCLUDED_SHARED_BINDINGS_NVM_BYTEARRAY_H__ +#define __MICROPY_INCLUDED_SHARED_BINDINGS_NVM_BYTEARRAY_H__ + +#include "common-hal/nvm/ByteArray.h" + +const mp_obj_type_t nvm_bytearray_type; + +uint32_t common_hal_nvm_bytearray_get_length(nvm_bytearray_obj_t *self); + +bool common_hal_nvm_bytearray_set_bytes(nvm_bytearray_obj_t *self, + uint32_t start_index, uint8_t* values, uint32_t len); +// len and values are intentionally swapped to signify values is an output and +// also leverage the compiler to validate uses are expected. +void common_hal_nvm_bytearray_get_bytes(nvm_bytearray_obj_t *self, + uint32_t start_index, uint32_t len, uint8_t* values); + +#endif // __MICROPY_INCLUDED_SHARED_BINDINGS_NVM_BYTEARRAY_H__ diff --git a/shared-bindings/nvm/__init__.c b/shared-bindings/nvm/__init__.c new file mode 100644 index 0000000000..8caf33ffb4 --- /dev/null +++ b/shared-bindings/nvm/__init__.c @@ -0,0 +1,62 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Scott Shawcroft 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 "py/obj.h" +#include "py/mphal.h" +#include "py/runtime.h" + +#include "shared-bindings/nvm/__init__.h" +#include "shared-bindings/nvm/ByteArray.h" + +//| :mod:`nvm` --- Non-volatile memory +//| =========================================================== +//| +//| .. module:: nvm +//| :synopsis: Non-volatile memory +//| :platform: SAMD21 +//| +//| The `nvm` module allows you to store whatever raw bytes you wish in a +//| reserved section non-volatile memory. +//| + +//| Libraries +//| +//| .. toctree:: +//| :maxdepth: 3 +//| +//| ByteArray +STATIC const mp_rom_map_elem_t nvm_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_nvm) }, + { MP_ROM_QSTR(MP_QSTR_ByteArray), MP_ROM_PTR(&nvm_bytearray_type) }, +}; + +STATIC MP_DEFINE_CONST_DICT(nvm_module_globals, nvm_module_globals_table); + +// cpy prefix is used to prevent collision with nvm_module global in ASF. +const mp_obj_module_t cpy_nvm_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&nvm_module_globals, +}; diff --git a/shared-bindings/nvm/__init__.h b/shared-bindings/nvm/__init__.h new file mode 100644 index 0000000000..9e17999747 --- /dev/null +++ b/shared-bindings/nvm/__init__.h @@ -0,0 +1,30 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Scott Shawcroft 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 SHARED_BINDINGS_NVM_H +#define SHARED_BINDINGS_NVM_H + +#endif // SHARED_BINDINGS_NVM_H diff --git a/tests/circuitpython/nvm_not_present.py b/tests/circuitpython/nvm_not_present.py new file mode 100644 index 0000000000..141e60b01e --- /dev/null +++ b/tests/circuitpython/nvm_not_present.py @@ -0,0 +1,6 @@ +import skip_if +skip_if.board_not_in("gemma_m0", "trinket_m0") + +import microcontroller + +assert(microcontroller.nvm == None) diff --git a/tests/circuitpython/nvm_present.py b/tests/circuitpython/nvm_present.py new file mode 100644 index 0000000000..a2cf3a22c2 --- /dev/null +++ b/tests/circuitpython/nvm_present.py @@ -0,0 +1,22 @@ +import skip_if +skip_if.board_not_in("metro_m0_express", "feather_m0_express", "circuitplayground_express") + +import microcontroller +import random +nvm = microcontroller.nvm + +len(nvm) + +single = random.randint(0, 255) +nvm[1] = single +assert(nvm[1] == single) + +nvm[0] = single +assert(nvm[0] == single) + +b = bytearray() +for i in range(10): + b.append(random.randint(0, 255)) + +microcontroller.nvm[10:20] = b +assert(microcontroller.nvm[10:20] == b) diff --git a/tests/skip_if.py b/tests/skip_if.py index f6d22b4c1f..7b1e068714 100644 --- a/tests/skip_if.py +++ b/tests/skip_if.py @@ -52,6 +52,17 @@ def board_in(*board): if test_env.board in board: skip() +def board_not_in(*board): + try: + import test_env + except ImportError: + class Env: + def __init__(self, board): + self.board = board + test_env = Env("unknown") + if test_env.board not in board: + skip() + def no_cpython_compat(): try: try: