示例#1
0
    def st_ptr_inc(self, data):
        """
        Store data to the pointer location with pointer post-increment
        :param data: data to store
        """
        self.logger.debug("ST8 to *ptr++")
        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC | constants.UPDI_DATA_8,
                            data[0]])
        response = self.updi_phy.receive(1)

        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
            if len(response)  > 0:
                self.logger.error("Expecting ACK after ST8 *ptr++. Got %d.", num, request[0])
            else:
                self.logger.error("Expecting ACK after ST8 *ptr++. Got nothing.", num)
            raise PymcuprogError("ACK error with st_ptr_inc")
        num = 1
        while num < len(data):
            self.updi_phy.send([data[num]])
            response = self.updi_phy.receive(1)

            if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
                if len(response)  > 0:
                    self.logger.error("Expecting ACK after ST8 *ptr++, after byte %d. Got 0x%00X}", num, request[0])
                else:
                    self.logger.error("Expecting ACK after ST8 *ptr++, after byte %d. Got nothing", num)
                raise PymcuprogError("Error with st_ptr_inc")
            num += 1
    def _st_data_phase(self, values):
        """
        Performs data phase of transaction:
        receive ACK
        send data
        :param values: bytearray of value(s) to send
        """
        response = self.updi_phy.receive(1)
        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
            if len(response >= 0):
                self.logger.error("expecting ACK after ST, but got: %02x",
                                  response[0])
            else:
                self.logger.error("expecting ACK after ST, got nothing.")
            raise PymcuprogError("Error with st")

        self.updi_phy.send(values)
        response = self.updi_phy.receive(1)
        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
            if len(response >= 0):
                self.logger.error(
                    "expecting ACK after ST value, but got: %02x", response[0])
            else:
                self.logger.error("expecting ACK after ST value, got nothing.")
            raise PymcuprogError("Error with st")
示例#3
0
    def st_ptr_inc16(self, data):
        """
        Store a 16-bit word value to the pointer location with pointer post-increment
        :param data: data to store
        """
        self.logger.debug("ST16 to *ptr++")
        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC |
                            constants.UPDI_DATA_16, data[0], data[1]])
        response = self.updi_phy.receive(1)

        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
            if len(response)  > 0:
                self.logger.error("Expecting ACK after ST16 *ptr++. Got {}}", response[0])
            else:
                self.logger.error("Expecting ACK after ST16 *ptr++. Got nothing")
            raise PymcuprogError("Error with st_ptr_inc16")

        num = 2
        while num < len(data):
            self.updi_phy.send([data[num], data[num + 1]])
            response = self.updi_phy.receive(1)

            if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
                if len(response)  > 0:
                    self.logger.error("Expecting ACK after ST16 *ptr++, after word %d.  0x%00X}", num, request[0])
                else:
                    self.logger.error("Expecting ACK after ST16 *ptr++, after word %d. Got nothing", num)
                raise PymcuprogError("Error with st_ptr_inc16")
            num += 2
示例#4
0
    def write_fuse(self, address, data):
        """
        Writes one fuse value (v0)
        :param address: address to write to
        :param data: data to write
        """

        # Check that NVM controller is ready
        if not self.wait_flash_ready():
            raise PymcuprogError(
                "Timeout waiting for flash ready before page buffer clear ")

        # Write address to NVMCTRL ADDR
        self.logger.debug("Load NVM address")
        self.readwrite.write_byte(
            self.device.nvmctrl_address + constants.UPDI_NVMCTRL_ADDRL,
            address & 0xFF)
        self.readwrite.write_byte(
            self.device.nvmctrl_address + constants.UPDI_NVMCTRL_ADDRH,
            (address >> 8) & 0xFF)

        # Write data
        self.logger.debug("Load fuse data")
        self.readwrite.write_byte(
            self.device.nvmctrl_address + constants.UPDI_NVMCTRL_DATAL,
            data[0] & 0xFF)

        # Execute
        self.logger.debug("Execute fuse write")
        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_WRITE_FUSE)

        if not self.wait_flash_ready():
            raise PymcuprogError(
                "Timeout waiting for flash ready before page buffer clear ")
示例#5
0
    def st_ptr_inc(self, data):
        """
        Store data to the pointer location with pointer post-increment

        :param data: data to store
        """
        self.logger.debug("ST8 to *ptr++")
        self.updi_phy.send([
            constants.UPDI_PHY_SYNC,
            constants.UPDI_ST | constants.UPDI_PTR_INC | constants.UPDI_DATA_8,
            data[0]
        ])
        response = self.updi_phy.receive(1)

        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
            raise PymcuprogError("ACK error with st_ptr_inc")

        num = 1
        while num < len(data):
            self.updi_phy.send([data[num]])
            response = self.updi_phy.receive(1)

            if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
                raise PymcuprogError("Error with st_ptr_inc")
            num += 1
示例#6
0
    def write_nvm(self,
                  address,
                  data,
                  use_word_access,
                  nvmcommand=constants.UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_WRITE):
        """
        Writes a page of data to NVM (v3)

        By default the PAGE_WRITE command is used, which
        requires that the page is already erased.
        By default word access is used (flash)

        :param address: address to write to
        :param data: data to write
        :param use_word_access: write whole words?
        :param nvmcommand: command to use for commit
        """

        # Check that NVM controller is ready
        if not self.wait_nvm_ready():
            raise PymcuprogError(
                "Timeout waiting for NVM controller to be ready before page buffer clear"
            )

        # Clear the page buffer
        self.logger.debug("Clear page buffer")
        self.execute_nvm_command(
            constants.UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_BUFFER_CLEAR)

        # Wait for NVM controller to be ready
        if not self.wait_nvm_ready():
            raise PymcuprogError(
                "Timeout waiting for NVM controller to be ready after page buffer clear"
            )

        # Load the page buffer by writing directly to location
        if use_word_access:
            self.readwrite.write_data_words(address, data)
        else:
            self.readwrite.write_data(address, data)

        # Write the page to NVM, maybe erase first
        self.logger.debug("Committing data")
        self.execute_nvm_command(nvmcommand)

        # Wait for NVM controller to be ready again
        if not self.wait_nvm_ready():
            raise PymcuprogError(
                "Timeout waiting for NVM controller to be ready after page write"
            )

        # Remove command
        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD)
示例#7
0
    def _st_data_phase(self, values):
        """
        Performs data phase of transaction:
        receive ACK
        send data
        :param values: bytearray of value(s) to send
        """
        response = self.updi_phy.receive(1)
        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
            raise PymcuprogError("Error with st")

        self.updi_phy.send(values)
        response = self.updi_phy.receive(1)
        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
            raise PymcuprogError("Error with st")
示例#8
0
    def write_data(self, address, data):
        """
        Writes a number of bytes to memory

        :param address: address to write to
        :param data: data to write
        """
        # Special case of 1 byte
        if len(data) == 1:
            return self.datalink.st(address, data[0])
        # Special case of 2 byte
        if len(data) == 2:
            self.datalink.st(address, data[0])
            return self.datalink.st(address + 1, data[1])

        # Range check
        if len(data) > constants.UPDI_MAX_REPEAT_SIZE:
            raise PymcuprogError("Invalid length")

        # Store the address
        self.datalink.st_ptr(address)

        # Fire up the repeat
        self.datalink.repeat(len(data))
        return self.datalink.st_ptr_inc(data)
    def memory_info_by_address_range(self,
                                     start,
                                     stop,
                                     address_type=DeviceMemoryInfoKeys.ADDRESS,
                                     size_type=DeviceMemoryInfoKeys.SIZE):
        """
        Returns a list of all memories applicable for the address range(start, stop)

        :param start: Start address (byte)
        :param stop: End address (byte)
        :param address_type: Selects between normal addresses and addresses used in hex files
            (address vs hexfile_address)
        :param size_type: Selects between normal size and size used in hexfiles (size vs hexfile_size)
        """
        # We do not support negative memory ranges
        if start > stop:
            raise PymcuprogError(
                "Cannot parse reverse memory range {} to {}".format(
                    start, stop))

        memtypes = []

        # Loop through all known memory types for this device
        for memtype in self.mem_by_name:
            address = self.mem_by_name[memtype][address_type]
            size = self.mem_by_name[memtype][size_type]

            # Check if any of the addresses between start and stop is within the memory type range
            if start < address + size and stop > address:
                memtypes.append(self.mem_by_name[memtype])
        return memtypes
示例#10
0
    def write_data_words(self, address, data, blocksize):
        """
        Writes a number of words to memory
        :param address: address to write to
        :param data: data to write
        :blocksize: max number of bytes being sent
        """
        # Special-case of 1 word
        if len(data) == 2:
            value = data[0] + (data[1] << 8)
            return self.datalink.st16(address, value)

        # Range check
        if len(data) > constants.UPDI_MAX_REPEAT_SIZE << 1:
            raise PymcuprogError("Invalid length")

        # Store the address
        self.datalink.st_ptr(address)

        # For performance, we want to do this with Response Signature Disable set, otherwise the USB Serial latency kills you
        # Just setting RSD here then turning it off at the end - it helps a lot, but you run up against the USB latency. So
        # now, EVERYTHING was moved into the st_ptr_inc16_RSD() function. Unless blocksize precludes it, the whole thing
        # is sent to the serial adapter in a single transfer.
        # the st_pty_inc16_RSD routine does the repeat and rsd enable/disable stu
        return self.datalink.st_ptr_inc16_RSD(data, blocksize)
示例#11
0
    def load_device_model(self, device_name, packpath):
        """
        Load the device model

        :param device_name: device name
        :param packpath: path to pack
        """

        # Locate and load the model from the pack

        if packpath is None:
            raise PymcuprogError("No path to pack repo provided!")

        # Import from pack.
        sys.path.append(os.path.normpath(packpath))
        sys.path.append(os.path.normpath(packpath + "//common"))

        # Use tool-type indicating a Config generator
        try:
            from debugprovider import ConfigGeneratorTool #pylint: disable=import-error, import-outside-toplevel
        except ModuleNotFoundError:
            raise PymcuprogError("Unable to import debugprovider from '{}'! Is this path correct?".format(packpath))

        self.tool = ConfigGeneratorTool()

        # Load the programming model
        programmer = Programmer(self.tool)
        # Load the device
        programmer.load_device(device_name)
        # Setup the device
        programmer.setup_device(interface=None, packpath=packpath)
        # Fetch the memory model
        self.device_memory_info = programmer.get_device_memory_info()
        # Extract the pic from level n of the stack
        self.pic = programmer.device_model.pic
        # Extract the source from level n+1 of the same stack
        self.source = inspect.getsource(programmer.device_model.pic.device_model)

        # Check to see if this device model can be abstracted:
        # Some devices are so antiquated that they only have support for incrementing the PC and reseting to 0
        # These devices currently abstract in Python in such a way that drag and drop will not work with these scripts.
        if 'RESET_ADDRESS' in dir(self.pic.device_model):
            raise PymcuprogError("This device model does not support drag and drop by primitives")
示例#12
0
 def key(self, size, key):
     """
     Write a key
     :param size: size of key (0=64B, 1=128B, 2=256B)
     :param key: key value
     """
     self.logger.debug("Writing key")
     if len(key) != 8 << size:
         raise PymcuprogError("Invalid KEY length!")
     self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_KEY | constants.UPDI_KEY_KEY | size])
     self.updi_phy.send(list(reversed(list(key))))
示例#13
0
 def init_datalink(self):
     """
     Init DL layer
     """
     self._init_session_parameters()
     # Check
     if not self._check_datalink():
         # Send double break if all is not well, and re-check
         self.updi_phy.send_double_break()
         self._init_session_parameters()
         if not self._check_datalink():
             raise PymcuprogError("UPDI initialisation failed")
示例#14
0
    def unlock(self):
        """
        Unlock by chip erase
        """
        # Put in the key
        self.readwrite.write_key(constants.UPDI_KEY_64, constants.UPDI_KEY_CHIPERASE)

        # Check key status
        key_status = self.readwrite.read_cs(constants.UPDI_ASI_KEY_STATUS)
        self.logger.debug("Key status = 0x%02X", key_status)

        if not key_status & (1 << constants.UPDI_ASI_KEY_STATUS_CHIPERASE):
            raise PymcuprogError("Key not accepted")

        # Toggle reset
        self.reset(apply_reset=True)
        self.reset(apply_reset=False)

        # And wait for unlock
        if not self.wait_unlocked(100):
            raise PymcuprogError("Failed to chip erase using key")
示例#15
0
 def st_ptr(self, address):
     """
     Set the pointer location
     :param address: address to write
     """
     self.logger.info("ST to ptr")
     self.updi_phy.send(
         [constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_ADDRESS | constants.UPDI_DATA_24,
          address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF])
     response = self.updi_phy.receive(1)
     if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
         raise PymcuprogError("Error with st_ptr")
示例#16
0
    def ldcs(self, address):
        """
        Load data from Control/Status space
        :param address: address to load
        """
        self.logger.debug("LDCS from 0x%02X", address)
        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_LDCS | (address & 0x0F)])
        response = self.updi_phy.receive(self.LDCS_RESPONSE_BYTES)
        numbytes_received = len(response)
        if numbytes_received != self.LDCS_RESPONSE_BYTES:
            raise PymcuprogError("Unexpected number of bytes in response: "
                                 "{} byte(s) expected {} byte(s)".format(numbytes_received, self.LDCS_RESPONSE_BYTES))

        return response[0]
示例#17
0
    def write_fuse(self, address, data, write_delay=1):
        """
        Writes one fuse value (v0)
        :param address: address to write to
        :param data: data to write
        :param write_delay: only default (1) is used ever. pause after every write, as fusewrite failures have been encountered without it.
        """

        # Check that NVM controller is ready
        if not self.wait_flash_ready():
            raise PymcuprogError(
                "Timeout waiting for flash ready before page buffer clear ")

        # Write address to NVMCTRL ADDR
        self.logger.debug("Load NVM address")
        self.readwrite.write_byte(
            self.device.nvmctrl_address + constants.UPDI_NVMCTRL_ADDRL,
            address & 0xFF)
        self.readwrite.write_byte(
            self.device.nvmctrl_address + constants.UPDI_NVMCTRL_ADDRH,
            (address >> 8) & 0xFF)

        # Write data
        self.logger.debug("Load fuse data")
        self.readwrite.write_byte(
            self.device.nvmctrl_address + constants.UPDI_NVMCTRL_DATAL,
            data[0] & 0xFF)

        # Execute
        self.logger.debug("Execute fuse write")
        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_WRITE_FUSE)

        if write_delay > 0:
            pause_mod.milliseconds(write_delay)
        if not self.wait_flash_ready():
            raise PymcuprogError(
                "Timeout waiting for flash ready before page buffer clear ")
示例#18
0
    def process_programming_functions(self):
        """
        Crunch through all programming functions and accumulate results
        """
        if not self.pic:
            raise PymcuprogError("Device model not loaded!")

        # Enter TMOD
        self.pic.enter_tmod()
        # Read ID
        self.pic.read_id()
        # Exit TMOD
        self.pic.exit_tmod()
        # Erase
        self.pic.erase()

        # Parameterised

        # This import must be late as it depends on the common folder in the packpath which is not available until
        # after load_device_model has been called
        from primitiveutils import ParametricValueToken #pylint: disable=import-error, import-outside-toplevel
        # Flash
        if MemoryNames.FLASH in self.device_memory_info.mem_by_name:
            flash_info = self.device_memory_info.memory_info_by_name(MemoryNames.FLASH)
            flash_page_size = flash_info[DeviceMemoryInfoKeys.PAGE_SIZE]
            self.pic._write_flash_page(byte_address=ParametricValueToken(ParametricValueToken.TOKEN_ADDRESS_LE32),
                                        data=bytearray(flash_page_size))
        # Config
        if MemoryNames.CONFIG_WORD in self.device_memory_info.mem_by_name:
            config_words_info = self.device_memory_info.memory_info_by_name(MemoryNames.CONFIG_WORD)
            config_words_write_size = config_words_info[DeviceMemoryInfoKeys.WRITE_SIZE]
            self.pic._write_config_word(byte_address=ParametricValueToken(ParametricValueToken.TOKEN_ADDRESS_LE32),
                                        data=bytearray(config_words_write_size))
        # EEPROM
        if MemoryNames.EEPROM in self.device_memory_info.mem_by_name:

            eeprom_info = self.device_memory_info.memory_info_by_name(MemoryNames.EEPROM)
            eeprom_write_size = eeprom_info[DeviceMemoryInfoKeys.WRITE_SIZE]
            self.pic._write_eeprom_block(byte_address=ParametricValueToken(ParametricValueToken.TOKEN_ADDRESS_LE32),
                                         data=bytearray(eeprom_write_size))

        # User IDs
        if MemoryNames.USER_ID in self.device_memory_info.mem_by_name:
            user_id_info = self.device_memory_info.memory_info_by_name(MemoryNames.USER_ID)
            user_id_write_size = user_id_info[DeviceMemoryInfoKeys.WRITE_SIZE]
            self.pic._write_user_id_word(byte_address=ParametricValueToken(ParametricValueToken.TOKEN_ADDRESS_LE32),
                                         data=bytearray(user_id_write_size))
示例#19
0
    def read_data(self, address, size):
        """
        Reads a number of bytes of data from UPDI
        :param address: address to write to
        :param size: number of bytes to read
        """
        self.logger.debug("Reading %d bytes from 0x%04X", size, address)
        # Range check
        if size > constants.UPDI_MAX_REPEAT_SIZE:
            raise PymcuprogError("Cant read that many bytes in one go")

        # Store the address
        self.datalink.st_ptr(address)

        # Fire up the repeat
        if size > 1:
            self.datalink.repeat(size)

        # Do the read(s)
        return self.datalink.ld_ptr_inc(size)
示例#20
0
    def read_data_words(self, address, words):
        """
        Reads a number of words of data from UPDI
        :param address: address to write to
        :param words: number of words to read
        """
        self.logger.debug("Reading %d words from 0x%04X", words, address)

        # Range check
        if words > constants.UPDI_MAX_REPEAT_SIZE:
            raise PymcuprogError("Cant read that many words in one go")

        # special case for single word - so we can optimize ld_ptr_inc16 for >1 word to improve performance.
        if words == 1:
            return self.datalink.ld16(address)

        # Otherwise, store the address
        self.datalink.st_ptr(address)

        # For performance, managing repeat count is done in ld_ptr_inc16()
        return self.datalink.ld_ptr_inc16(words)
示例#21
0
    def read_data_words(self, address, words):
        """
        Reads a number of words of data from UPDI
        :param address: address to write to
        :param words: number of words to read
        """
        self.logger.debug("Reading %d words from 0x%04X", words, address)

        # Range check
        if words > constants.UPDI_MAX_REPEAT_SIZE >> 1:
            raise PymcuprogError("Cant read that many words in one go")

        # Store the address
        self.datalink.st_ptr(address)

        # Fire up the repeat
        if words > 1:
            self.datalink.repeat(words)

        # Do the read
        return self.datalink.ld_ptr_inc16(words)
示例#22
0
    def write_data_words(self, address, data):
        """
        Writes a number of words to memory
        :param address: address to write to
        :param data: data to write
        """
        # Special-case of 1 word
        if len(data) == 2:
            value = data[0] + (data[1] << 8)
            return self.datalink.st16(address, value)

        # Range check
        if len(data) > constants.UPDI_MAX_REPEAT_SIZE << 1:
            raise PymcuprogError("Invalid length")

        # Store the address
        self.datalink.st_ptr(address)

        # Fire up the repeat
        self.datalink.repeat(len(data) >> 1)
        return self.datalink.st_ptr_inc16(data)
    def memory_info_by_address(self,
                               byte_address,
                               address_type=DeviceMemoryInfoKeys.ADDRESS,
                               size_type=DeviceMemoryInfoKeys.SIZE):
        """
        Returns information about the memory type for a given byte address

        :param byte_address: Memory address to check
        :param address_type: Selects between normal addresses and addresses used in hex files
            (ADDRESS vs HEXFILE_ADDRESS)
        :param size_type: Selects between normal size and size used in hexfiles (size vs hexfile_size)
       """
        memtype = None
        for memory in self.mem_by_name:
            if self.mem_by_name[memory][address_type] <= byte_address < \
                    self.mem_by_name[memory][address_type] + self.mem_by_name[memory][size_type]:
                if memtype is not None:
                    raise PymcuprogError(
                        "Duplicate memory area found for byte address '{}'".
                        format(byte_address))
                memtype = self.mem_by_name[memory]
        return memtype
示例#24
0
    def write_nvm(self,
                  address,
                  data,
                  use_word_access,
                  nvmcommand=constants.UPDI_V0_NVMCTRL_CTRLA_WRITE_PAGE,
                  blocksize=2,
                  bulkwrite=0,
                  pagewrite_delay=0):
        """
        Writes a page of data to NVM (v0)

        By default the PAGE_WRITE command is used, which
        requires that the page is already erased.
        By default word access is used (flash)
        :param address: address to write to
        :param data: data to write
        :param use_word_access: write whole words?
        :param nvmcommand: command to use for commit
        :param bulkwrite: Passed down from nvmserialupdi 0 = normal or single write.
            1 means it's part of writing the whole flash.
            In that case we only st ptr if address = 0.
        :param pagewrite_delay: (ms) delay before pagewrite

        """

        # unless we are in a bulk (whole flash) write, in which case we skip almost everything.
        if (bulkwrite == 0
            ) or address == 0x8000 or address == 0x4000 or not use_word_access:
            # Check that NVM controller is ready
            # I will grudgingly check this at the very start. I am extremely skeptical about the usefulness of this test.
            # If it's not ready, they'll get another error will they not? Every command like this costs about a half second
            # on every upload when using serialupdi - at any bsaud rate, assuming 256 pages. It's all USB latency.
            if not self.wait_flash_ready():
                raise PymcuprogError(
                    "Timeout waiting for flash ready before page buffer clear "
                )
                # Clear the page buffer
            self.logger.debug("Clear page buffer")
            self.execute_nvm_command(
                constants.UPDI_V0_NVMCTRL_CTRLA_PAGE_BUFFER_CLR)

            # Wait for NVM controller to be ready
            if not self.wait_flash_ready():
                raise PymcuprogError(
                    "Timeout waiting for flash ready after page buffer clear")

        # Load the page buffer by writing directly to location
        if use_word_access:
            self.readwrite.write_data_words(address, data, blocksize)
        else:
            self.readwrite.write_data(address, data)

        # Write the page to NVM, maybe erase first
        self.logger.debug("Committing data")

        self.execute_nvm_command(nvmcommand)

        if pagewrite_delay > 0:
            pause_mod.milliseconds(pagewrite_delay)
        # SACRIFICES SPEED FOR COMPATIBILITY - above line should execute only when --pagepause command line parameter is 1 or more (default 0), so we can adjust it externally
        if not bulkwrite == 1:
            # do a final NVM status check only if not doing a bulk write, or after the last chunk (when bulkwrite = 2)
            # not doing this every page made uploads about 15% faster
            if not self.wait_flash_ready():
                raise PymcuprogError(
                    "Timeout waiting for flash ready after page write ")
示例#25
0
    def write_nvm(self,
                  address,
                  data,
                  use_word_access,
                  nvmcommand=constants.UPDI_V0_NVMCTRL_CTRLA_WRITE_PAGE,
                  blocksize=2,
                  bulkwrite=0):
        """
        Writes a page of data to NVM (v0)

        By default the PAGE_WRITE command is used, which
        requires that the page is already erased.
        By default word access is used (flash)
        :param address: address to write to
        :param data: data to write
        :param use_word_access: write whole words?
        :param nvmcommand: command to use for commit
        :param bulkwrite: Passed down from nvmserialupdi 0 = normal or single write.
            1 means it's part of writing the whole flash.
            In that case we only st ptr if address = 0.

        """

        # unless we are in a bulk (whole flash) write, in which case we skip almost everything.
        if (bulkwrite == 0
            ) or address == 0x8000 or address == 0x4000 or not use_word_access:
            # Check that NVM controller is ready
            # I will grudgingly check this at the very start. I am extremely skeptical about the usefulness of this test.
            # If it's not ready, they'll get another error will they not? Every command like this costs about a half second
            # on every upload when using serialupdi - at any bsaud rate, assuming 256 pages. It's all USB latency.
            if not self.wait_flash_ready():
                raise PymcuprogError(
                    "Timeout waiting for flash ready before page buffer clear "
                )
                # Clear the page buffer
            self.logger.debug("Clear page buffer")
            self.execute_nvm_command(
                constants.UPDI_V0_NVMCTRL_CTRLA_PAGE_BUFFER_CLR)

            # Wait for NVM controller to be ready
            if not self.wait_flash_ready():
                raise PymcuprogError(
                    "Timeout waiting for flash ready after page buffer clear")

        # Load the page buffer by writing directly to location
        if use_word_access:
            self.readwrite.write_data_words(address, data, blocksize)
        else:
            self.readwrite.write_data(address, data)

        # Write the page to NVM, maybe erase first
        self.logger.debug("Committing data")

        self.execute_nvm_command(nvmcommand)
        # I examine the logs, there are never any cases whee more than one read of this is done.
        # So since this isn't meeded to handle normal operations, only error conditions,
        # we can let verify catch those - it's worth less helpful information on rare errors - difference in upload speed can be up to 15%
        # every USB Latency Period that is removed from the stuff that haoppens every page cuts more than a half second off the upload time!
        if not bulkwrite == 1:
            # do a final NVM status check only if not doing a bulk write, or after the last chunk (when bulkwrite = 2)
            # not doing this every page made uploads about 15% faster
            if not self.wait_flash_ready():
                raise PymcuprogError(
                    "Timeout waiting for flash ready after page write ")