SpenceKonde / megaTinyCore

Arduino core for the tinyAVR 0/1/2-series - Ones's digit 2,4,5,7 (pincount, 8,14,20,24), tens digit 0, 1, or 2 (featureset), preceded by flash in kb. Library maintainers: porting help available!
Other
544 stars 141 forks source link

Writing Flash Memory on ATTiny 3217 - Part 2 #950

Closed Unreal-Dan closed 11 months ago

Unreal-Dan commented 1 year ago

First, I'm so sorry that I have to continue bothering you Spence, I don't know where else to turn at this point.

It seems as though the compile issues and problem with the previous post weren't actually related to the issues I was having with writing flash.

Since my previous post about writing flash memory I have learned a lot, I have come to realize that we are not using a bootloader nor do we need one. We do not need to be able to upload firmwares on the fly, quite simply I need a little more storage than the available 256 bytes of EEPROM.

We do not have a serial connection to the chip so optiboot isn't even really an option, only updi via jtag2updi.

I have studied the optiboot_x source code and I see how it is re-writing flash memory here:

https://github.com/Optiboot/optiboot/blob/master/optiboot/bootloaders/optiboot/optiboot_x.c#L509

I am attempting to write out flash memory in the exact same fashion but all attempts fail.

It seems as though NVMCTRL.STATUS is actually WRERROR after I try to perform an erase + write page with this code:

// store a serial buffer to storage
bool Storage::write(ByteStream &buffer)
{
  // Check size
  const uint16_t size = buffer.rawSize();
  if (!size || size > STORAGE_SIZE) {
    ERROR_LOG("Buffer too big for storage space");
    return false;
  }
  uint16_t pages = (size / PROGMEM_PAGE_SIZE) + 1;
  const uint8_t *buf = (const uint8_t *)buffer.rawData();
  for (uint16_t i = 0; i < pages; i++) {
    // don't read past the end of the input buffer
    uint16_t target = i * PROGMEM_PAGE_SIZE;
    uint16_t s = ((target + PROGMEM_PAGE_SIZE) > size) ? (size % PROGMEM_PAGE_SIZE) : PROGMEM_PAGE_SIZE;
    for (uint16_t j = 0; j < s; ++j) {
      ((uint8_t *)storage_data)[target + j] = buf[target + j];
    }
    // Erase + write the flash page
    _PROTECTED_WRITE_SPM(NVMCTRL.CTRLA, 0x3);
    while (NVMCTRL.STATUS & 0x3);
  }
  return true;
}

I am using the raw values 0x3 for CTRLA and NVMCTRL.STATUS because I don't trust the macros in the headers I have available, some of them seemed to be wrong when I compared to the data sheet.

The datasheet is quite clear about which bits are which values so I am using constants like 0x3 for:

     _PROTECTED_WRITE_SPM(NVMCTRL.CTRLA, 0x3);
    while (NVMCTRL.STATUS & 0x3);

As per the data sheet: https://ww1.microchip.com/downloads/aemDocuments/documents/MCU08/ProductDocuments/DataSheets/ATtiny3216-17-DataSheet-DS40002205A.pdf

STATUS:

Bit 2 – WRERROR Write Error
This bit will read ‘1’ when a write error has happened. A write error could be writing to different sections before doing
a page write or writing to a protected area. This bit is valid for the last operation.
Bit 1 – EEBUSY EEPROM Busy
This bit will read ‘1’ when the EEPROM is busy with a command.
Bit 0 – FBUSY Flash Busy
This bit will read ‘1’ when the Flash is busy with a command.

and CTRLA:

Bits 2:0 – CMD[2:0] Command
Write this bit field to issue a command. The Configuration Change Protection key for self-programming (SPM) has to
be written within four instructions before this write.
Value Name Description
0x0 - No command
0x1 WP Write page buffer to memory (NVMCTRL.ADDR selects which memory)
0x2 ER Erase page (NVMCTRL.ADDR selects which memory)
0x3 ERWP Erase and write page (NVMCTRL.ADDR selects which memory)
0x4 PBC Page buffer clear
0x5 CHER Chip erase: Erase Flash and EEPROM (unless EESAVE in FUSE.SYSCFG is ‘1’)
0x6 EEER EEPROM Erase
0x7 WFU Write fuse (only accessible through UPDI)

I have tried so many different things at this point I'm not sure how I could even list all the different approaches I've taken without making a complete mess of this post.

The one thing that I have been able to do is get the EEPROM working, you can see the working EEPROM code in this specific branch of my project (specifically lines 56 and 74):

https://github.com/StoneOrbits/VortexEngine/blob/finger/VortexEngine/src/Storage/Storage.cpp

My latest attempt to make flash working is an ongoing work, but you can see a solid attempt in this specific commit here:

https://github.com/StoneOrbits/VortexEngine/blob/c0878c7735ab8d78ee75eb86311fc4339755be1e/VortexEngine/src/Storage/Storage.cpp

I am using a custom linker script that creates the section .storage and I am using a custom Makefile to run avr-gcc and avrdude with my own custom fuses:

upload: $(TARGET).hex
  $(AVRDUDE) $(AVRDUDE_FLAGS) \
    -Ufuse0:w:0b00000000:m \
    -Ufuse2:w:0x02:m \
    -Ufuse5:w:0b11000101:m \
    -Ufuse6:w:0x04:m \
    -Ufuse7:w:0x78:m \
    -Ufuse8:w:0x00:m \
    -Uflash:w:$(TARGET).hex:i

I have tried all kinds of different values for fuse7 but no matter what I set, and no matter which address of ram I target the flash rewriting won't seem to work.


I decided to take a look at EEPROM.h and see if I could understand why it might be able to work using similar operations with the NVMCTRL and I noticed this:

  EERef &operator = (uint8_t in)       {
    #ifdef MEGATINYCORE
    // I see no reason why eeprom_write_byte() won't corrupt EEPROM if an ISR tries to write at the wrong instant. The window is 1 clock, but not 0
    uint16_t adr = (uint16_t)MAPPED_EEPROM_START + (index & EEPROM_INDEX_MASK);
    __asm__ __volatile__(
      "ldi r30, 0x00"     "\n\t" // point the Z register at NVMCTRL.
      "ldi r31, 0x10"     "\n\t"
      "in r0, 0x3f"       "\n\t" // read the SREG into r0 to narrow the window between sbrc and cli.
      "ldd r18, Z+2"      "\n\t" // read NVMCTRL.STATUS into r18
      "andi r18, 3"       "\n\t" // if NVMCTRL is busy....
      "brne .-6"          "\n\t" // repeat until it's not.
      "cli"               "\n\t" // disable interrupts. 3 clock window during which an interrupt couldstart write since we checked
      //                            but this just means millis will lose time - nvmctrl halts CPU to finish last write
      "st X, %0"          "\n\t" // write the value we were passed
      "ldi %0, 0x9D"      "\n\t" // CCP signature loaded in it's place
      "out 0x34, %0"      "\n\t" // protection enabled
      "ldi %0, 0x03"      "\n\t" // command loaded: page erase-write.
      "st Z, %0"          "\n\t" // write the page erase-write command to nvmctrl.ctrla
      "out 0x3f, r0"      "\n"   // restore SREG
      :"+d"(in)           // take the value we are writing in any upper register as read/write,
      : "x"(adr)          // and the address (not the index) in X
      : "r30", "r31", "r18");      // clobber Z and r18. We needed an upper register for the temporary value to andi it. I wonder if this will fix the eeprom bugs too?
    return *this;
    #else
    uint8_t oldSREG = SREG;
    while (NVMCTRL.STATUS & NVMCTRL_EEBUSY_bm);
    // check at start - not end. Makes writing single bytes fasterms
    // Note that we only have interrupts disabled for the dozen or so clock cycles
    // during which we *start* the write, not for the while loop, and we save SREG
    // before the while loop. That way there is only a 1 clock window where an
    // interrupt that starts a write will cause this write to halt the CPU
    // which would cause millis() to lose time.
    // Note that writing more than 1 byte in an ISR will **always** cause millis to lose time.
    cli();

    _PROTECTED_WRITE_SPM(NVMCTRL.CTRLA, NVMCTRL_CMD_NONE_gc);
    _PROTECTED_WRITE_SPM(NVMCTRL.CTRLA, NVMCTRL_CMD_EEERWR_gc);
    *(uint8_t *)(MAPPED_EEPROM_START + (index & EEPROM_INDEX_MASK)) = in;

    SREG = oldSREG; // restore SREG and interrupts
    return *this;
    #endif
  }

It looks to be a massive ifdef splitting off the EEPROM write function into a different assembly-only version for megatinycore?

Does this mean there was something special that you had to do, such that it had to be done in raw assembly?

Do I need something similar for rewriting the flash with the nvmctrl?

SpenceKonde commented 1 year ago

I thibk what you're missing is rhat you are trying to write to APPCODE from within APPCODE. Obviously no proper security model would permit this because it would allow a single code execution exploit to be leveraged into full control over the chip except for the bootloader itself. You can only write to sections of flash that are after CODEEND (Dx terminology) or APPEND when executing from APPCODE. The flash.h that uses optiboot gets around this by calling into the bootloader (BOOTCODE section) to make writes, since the bootloader section can write all of the flash except BOOTCODE. On dx, for bootloaderless arbitrary address flash writes (ie, allowing the app to scribble over itself) we declare the first 512b bootloader, and before enabling interrupts, redirect them to start at 0 instead of the start of the app and use unholy tricks to bamboozle the linker into ignoring its usual priority list (for exple, anything declared PROGMEM takes megapriority normal) and giving the required write instructions priority access to the first addresses after the vectors so they'll all be in the first 256 words( otherwise theyll give write ptotect errors when you try to write the page... I know how to do this but it is non-trivial, an I'm going to say there is insufficient demand to add this feature - unless someone were.willkng to pay for it.

Doing writes after APPEND from APPCODE is straightforward (but precluded by the way the core configures that parameter)

Doing writes to arbitrary addresses > 0x1FF is much harder.

Both require modifications to the core which I have had not a hint of demand for until your request; the first method, relatively minor. The second method...more considerable


Spence Konde Azzy’S Electronics

New products! Check them out at tindie.com/stores/DrAzzy GitHub: github.com/SpenceKonde ATTinyCore: Arduino support for almost every ATTiny microcontroller Contact: @.***

On Fri, May 5, 2023, 19:58 Unreal-Dan @.***> wrote:

First, I'm so sorry that I have to continue bothering you Spence, I don't know where else to turn at this point.

It seems as though the compile issues and problem with the previous post weren't actually related to the issues I was having with writing flash.

Since my previous post about writing flash memory I have learned a lot, I have come to realize that we are not using a bootloader nor do we need one. We do not need to be able to upload firmwares on the fly, quite simply I need a little more storage than the available 256 bytes of EEPROM.

We do not have a serial connection to the chip so optiboot isn't even really an option, only updi via jtag2updi.

I have studied the optiboot_x source code and I see how it is re-writing flash memory here:

https://github.com/Optiboot/optiboot/blob/master/optiboot/bootloaders/optiboot/optiboot_x.c#L509

I am attempting to write out flash memory in the exact same fashion but all attempts fail.

It seems as though NVMCTRL.STATUS is actually WRERROR after I try to perform an erase + write page with this code:

// store a serial buffer to storage bool Storage::write(ByteStream &buffer) { // Check size const uint16_t size = buffer.rawSize(); if (!size || size > STORAGE_SIZE) { ERROR_LOG("Buffer too big for storage space"); return false; } uint16_t pages = (size / PROGMEM_PAGE_SIZE) + 1; const uint8_t buf = (const uint8_t )buffer.rawData(); for (uint16_t i = 0; i < pages; i++) { // don't read past the end of the input buffer uint16_t target = i PROGMEM_PAGE_SIZE; uint16_t s = ((target + PROGMEM_PAGE_SIZE) > size) ? (size % PROGMEM_PAGE_SIZE) : PROGMEM_PAGE_SIZE; for (uint16_t j = 0; j < s; ++j) { ((uint8_t )storage_data)[target + j] = buf[target + j]; } // Erase + write the flash page _PROTECTED_WRITE_SPM(NVMCTRL.CTRLA, 0x3); while (NVMCTRL.STATUS & 0x3); } return true; }

I am using the raw values 0x3 for CTRLA and NVMCTRL.STATUS because I don't trust the macros in the headers I have available, some of them seemed to be wrong when I compared to the data sheet.

The datasheet is quite clear about which bits are which values so I am using constants like 0x3 for:

_PROTECTED_WRITE_SPM(NVMCTRL.CTRLA, 0x3);

while (NVMCTRL.STATUS & 0x3);

As per the data sheet: https://ww1.microchip.com/downloads/aemDocuments/documents/MCU08/ProductDocuments/DataSheets/ATtiny3216-17-DataSheet-DS40002205A.pdf

STATUS:

Bit 2 – WRERROR Write Error This bit will read ‘1’ when a write error has happened. A write error could be writing to different sections before doing a page write or writing to a protected area. This bit is valid for the last operation. Bit 1 – EEBUSY EEPROM Busy This bit will read ‘1’ when the EEPROM is busy with a command. Bit 0 – FBUSY Flash Busy This bit will read ‘1’ when the Flash is busy with a command.

and CTRLA:

Bits 2:0 – CMD[2:0] Command Write this bit field to issue a command. The Configuration Change Protection key for self-programming (SPM) has to be written within four instructions before this write. Value Name Description 0x0 - No command 0x1 WP Write page buffer to memory (NVMCTRL.ADDR selects which memory) 0x2 ER Erase page (NVMCTRL.ADDR selects which memory) 0x3 ERWP Erase and write page (NVMCTRL.ADDR selects which memory) 0x4 PBC Page buffer clear 0x5 CHER Chip erase: Erase Flash and EEPROM (unless EESAVE in FUSE.SYSCFG is ‘1’) 0x6 EEER EEPROM Erase 0x7 WFU Write fuse (only accessible through UPDI)

I have tried so many different things at this point I'm not sure how I could even list all the different approaches I've taken without making a complete mess of this post.

The one thing that I have been able to do is get the EEPROM working, you can see the working EEPROM code in this specific branch of my project:

https://github.com/StoneOrbits/VortexEngine/blob/finger/VortexEngine/src/Storage/Storage.cpp

My latest attempt to make flash working is an ongoing work, but you can see a solid attempt in this specific commit here:

https://github.com/StoneOrbits/VortexEngine/blob/c0878c7735ab8d78ee75eb86311fc4339755be1e/VortexEngine/src/Storage/Storage.cpp

I am using a custom linker script that creates the section .storage and I am using a custom Makefile to run avr-gcc and avrdude with my own custom fuses:

upload: $(TARGET).hex $(AVRDUDE) $(AVRDUDE_FLAGS) \ -Ufuse0:w:0b00000000:m \ -Ufuse2:w:0x02:m \ -Ufuse5:w:0b11000101:m \ -Ufuse6:w:0x04:m \ -Ufuse7:w:0x78:m \ -Ufuse8:w:0x00:m \ -Uflash:w:$(TARGET).hex:i

I have tried all kinds of different values for fuse7 but no matter what I set, and no matter which address of ram I target the flash rewriting won't seem to work.

I decided to take a look at EEPROM.h and see if I could understand why it might be able to work using similar operations with the NVMCTRL and I noticed this:

EERef &operator = (uint8_t in) {

ifdef MEGATINYCORE

// I see no reason why eeprom_write_byte() won't corrupt EEPROM if an ISR tries to write at the wrong instant. The window is 1 clock, but not 0
uint16_t adr = (uint16_t)MAPPED_EEPROM_START + (index & EEPROM_INDEX_MASK);
__asm__ __volatile__(
  "ldi r30, 0x00"     "\n\t" // point the Z register at NVMCTRL.
  "ldi r31, 0x10"     "\n\t"
  "in r0, 0x3f"       "\n\t" // read the SREG into r0 to narrow the window between sbrc and cli.
  "ldd r18, Z+2"      "\n\t" // read NVMCTRL.STATUS into r18
  "andi r18, 3"       "\n\t" // if NVMCTRL is busy....
  "brne .-6"          "\n\t" // repeat until it's not.
  "cli"               "\n\t" // disable interrupts. 3 clock window during which an interrupt couldstart write since we checked
  //                            but this just means millis will lose time - nvmctrl halts CPU to finish last write
  "st X, %0"          "\n\t" // write the value we were passed
  "ldi %0, 0x9D"      "\n\t" // CCP signature loaded in it's place
  "out 0x34, %0"      "\n\t" // protection enabled
  "ldi %0, 0x03"      "\n\t" // command loaded: page erase-write.
  "st Z, %0"          "\n\t" // write the page erase-write command to nvmctrl.ctrla
  "out 0x3f, r0"      "\n"   // restore SREG
  :"+d"(in)           // take the value we are writing in any upper register as read/write,
  : "x"(adr)          // and the address (not the index) in X
  : "r30", "r31", "r18");      // clobber Z and r18. We needed an upper register for the temporary value to andi it. I wonder if this will fix the eeprom bugs too?
return *this;
#else
uint8_t oldSREG = SREG;
while (NVMCTRL.STATUS & NVMCTRL_EEBUSY_bm);
// check at start - not end. Makes writing single bytes fasterms
// Note that we only have interrupts disabled for the dozen or so clock cycles
// during which we *start* the write, not for the while loop, and we save SREG
// before the while loop. That way there is only a 1 clock window where an
// interrupt that starts a write will cause this write to halt the CPU
// which would cause millis() to lose time.
// Note that writing more than 1 byte in an ISR will **always** cause millis to lose time.
cli();

_PROTECTED_WRITE_SPM(NVMCTRL.CTRLA, NVMCTRL_CMD_NONE_gc);
_PROTECTED_WRITE_SPM(NVMCTRL.CTRLA, NVMCTRL_CMD_EEERWR_gc);
*(uint8_t *)(MAPPED_EEPROM_START + (index & EEPROM_INDEX_MASK)) = in;

SREG = oldSREG; // restore SREG and interrupts
return *this;
#endif

}

It looks to be a massive ifdef splitting off the EEPROM write function into a different assembly-only version for megatinycore?

Does this mean there was something special that you had to do, such that it had to be done in raw assembly?

Do I need something similar for rewriting the flash with the nvmctrl?

— Reply to this email directly, view it on GitHub https://github.com/SpenceKonde/megaTinyCore/issues/950, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABTXEW7LORRIHUT4BVHHPWTXEWHZZANCNFSM6AAAAAAXXWO6P4 . You are receiving this because you are subscribed to this thread.Message ID: @.***>

Unreal-Dan commented 1 year ago

So yes, I recognize that I need to configure the fuses so that I can write to the separate section (app data), I tried to configure APPEND to all kinds of different values but based on my math 0x7C should leave me 0x400 before the end of the flash to use as extra storage. This doesn't seem to work.

I recognize that the core would need changes and I am willing to manage those changes myself.

I am actually using a custom Makefile that mimics the commands you run in Arduino, so that I can have complete control over all of the flags.

Here is the makefile (don't mind the full paths I will fix that later):

CC = C:/Users/danie/AppData/Local/Arduino15/packages/DxCore/tools/avr-gcc/7.3.0-atmel3.6.1-azduino6/bin/avr-gcc
LD = C:/Users/danie/AppData/Local/Arduino15/packages/DxCore/tools/avr-gcc/7.3.0-atmel3.6.1-azduino6/bin/avr-gcc
OBJCOPY = C:/Users/danie/AppData/Local/Arduino15/packages/DxCore/tools/avr-gcc/7.3.0-atmel3.6.1-azduino6/bin/avr-objcopy -v
AR = C:/Users/danie/AppData/Local/Arduino15/packages/DxCore/tools/avr-gcc/7.3.0-atmel3.6.1-azduino6/bin/avr-gcc-ar
SIZE = C:/Users/danie/AppData/Local/Arduino15/packages/DxCore/tools/avr-gcc/7.3.0-atmel3.6.1-azduino6/bin/avr-size
OBJDUMP = C:/Users/danie/AppData/Local/Arduino15/packages/DxCore/tools/avr-gcc/7.3.0-atmel3.6.1-azduino6/bin/avr-objdump
NM = C:/Users/danie/AppData/Local/Arduino15/packages/DxCore/tools/avr-gcc/7.3.0-atmel3.6.1-azduino6/bin/avr-nm
AVRDUDE =  C:/Users/danie/AppData/Local/Arduino15/packages/DxCore/tools/avrdude/6.3.0-arduino17or18/bin/avrdude

AVRDUDE_CONF = C:/Users/danie/source/repos/VortexAVR/VortexEngine/avrdude.conf
AVRDUDE_PORT = COM12
AVRDUDE_BAUDRATE = 115200
AVRDUDE_FLAGS = -C$(AVRDUDE_CONF) -v -pattiny3217 -cjtag2updi -P$(AVRDUDE_PORT) -b$(AVRDUDE_BAUDRATE)

ASMFLAGS = \
  -x assembler-with-cpp \
  -flto \
  -MMD \
  -mmcu=attiny3217 \
  -DF_CPU=20000000L \
  -DCLOCK_SOURCE=0 \
  -DTWI_MORS \
  -DMILLIS_USE_TIMERD0 \
  -DCORE_ATTACH_ALL \
  -DUSE_TIMERD0_PWM \
  -DARDUINO=10819 \
  -DARDUINO_AVR_ATtiny3217 \
  -DARDUINO_ARCH_MEGAAVR \
  -DMEGATINYCORE=\"2.6.7.1\" \
  -DMEGATINYCORE_MAJOR=2UL \
  -DMEGATINYCORE_MINOR=6UL \
  -DMEGATINYCORE_PATCH=7UL \
  -DMEGATINYCORE_RELEASED=1 \
  -DARDUINO_attinyxy7 \
  -I C:/Users/danie/AppData/Local/Arduino15/packages/megaTinyCore/hardware/megaavr/2.6.7/cores/megatinycore/api/deprecated \
  -I C:/Users/danie/AppData/Local/Arduino15/packages/megaTinyCore/hardware/megaavr/2.6.7/cores/megatinycore \
  -I C:/Users/danie/AppData/Local/Arduino15/packages/megaTinyCore/hardware/megaavr/2.6.7/variants/txy7

CFLAGS = \
  -Wall \
  -Os \
  -std=gnu++17 \
  -fpermissive \
  -Wno-sized-deallocation \
  -fno-exceptions \
  -ffunction-sections \
  -fdata-sections \
  -fno-threadsafe-statics \
  -Wno-error=narrowing \
  -MMD \
  -flto \
  -mrelax \
  -mmcu=attiny3217 \
  -DF_CPU=20000000L \
  -DCLOCK_SOURCE=0 \
  -DTWI_MORS \
  -DMILLIS_USE_TIMERD0 \
  -DCORE_ATTACH_ALL \
  -DUSE_TIMERD0_PWM \
  -DARDUINO=10819 \
  -DARDUINO_AVR_ATtiny3217 \
  -DARDUINO_ARCH_MEGAAVR \
  -DMEGATINYCORE=\"2.6.7.1\" \
  -DMEGATINYCORE_MAJOR=2UL \
  -DMEGATINYCORE_MINOR=6UL \
  -DMEGATINYCORE_PATCH=7UL \
  -DMEGATINYCORE_RELEASED=1 \
  -DARDUINO_attinyxy7  \
  -I C:/Users/danie/AppData/Local/Arduino15/packages/megaTinyCore/hardware/megaavr/2.6.7/cores/megatinycore/api/deprecated \
  -I C:/Users/danie/AppData/Local/Arduino15/packages/megaTinyCore/hardware/megaavr/2.6.7/cores/megatinycore \
  -I C:/Users/danie/AppData/Local/Arduino15/packages/megaTinyCore/hardware/megaavr/2.6.7/libraries/EEPROM/src/ \
  -I C:/Users/danie/AppData/Local/Arduino15/packages/megaTinyCore/hardware/megaavr/2.6.7/variants/txy7

#LDFLAGS = -mmcu=attiny3217 -nostartfiles -flto -fuse-linker-plugin -Wl,--gc-sections -Wl,--section-start=.text=0x0 -lm

LDFLAGS = -Wall -Os -flto -fuse-linker-plugin -Wl,--gc-sections -Wl,--section-start=.text=0x0 -mrelax -mmcu=attiny3217 -lm -Wl,-T,custom.xn

INCLUDES=\
        -I ./VortexEngine/src/ \
        -I ./VortexEngine/VortexLib/EngineDependencies/

ifneq ($(INCLUDES),)
    CFLAGS+=$(INCLUDES)
endif

# Source files
SRCS = \
        ....

OBJS = $(SRCS:.cpp=.o)

COREASM = \
        ./libraries/megatinycore/wiring_pulse.S

CORESRC = \
        ./libraries/megatinycore/abi.cpp \
        ./libraries/megatinycore/api/Common.cpp \
        ./libraries/megatinycore/api/IPAddress.cpp \
        ./libraries/megatinycore/api/PluggableUSB.cpp \
        ./libraries/megatinycore/api/Print.cpp \
        ./libraries/megatinycore/api/RingBuffer.cpp \
        ./libraries/megatinycore/api/Stream.cpp \
        ./libraries/megatinycore/api/String.cpp \
        ./libraries/megatinycore/ExtraWiring.cpp \
        ./libraries/megatinycore/main.cpp \
        ./libraries/megatinycore/new.cpp \
        ./libraries/megatinycore/Tone.cpp \
        ./libraries/megatinycore/UART.cpp \
        ./libraries/megatinycore/UART0.cpp \
        ./libraries/megatinycore/UART1.cpp \
        ./libraries/megatinycore/wiring_extra.cpp \
        ./libraries/megatinycore/WMath.cpp \
        ./appmain.cpp

CORESRCC = \
        ./libraries/megatinycore/hooks.c \
        ./libraries/megatinycore/WInterrupts.c \
        ./libraries/megatinycore/WInterrupts_PA.c \
        ./libraries/megatinycore/WInterrupts_PB.c \
        ./libraries/megatinycore/WInterrupts_PC.c \
        ./libraries/megatinycore/wiring.c \
        ./libraries/megatinycore/wiring_analog.c \
        ./libraries/megatinycore/wiring_digital.c \
        ./libraries/megatinycore/wiring_pulse.c \
        ./libraries/megatinycore/wiring_shift.c

COREOBJS = $(COREASM:.S=.o) $(CORESRC:.cpp=.o) $(CORESRCC:.c=.o)

# Target name
TARGET = main

all: $(TARGET).hex
        $(OBJDUMP) --disassemble --source --line-numbers --demangle --section=.text $(TARGET).elf > $(TARGET).lst
        #$(OBJDUMP) --disassemble --source --line-numbers --demangle --section=.storage $(TARGET).elf > $(TARGET)-storage.lst
        $(NM) --numeric-sort --line-numbers --demangle --print-size --format=s $(TARGET).elf > $(TARGET).map
        ./avrsize.sh

$(TARGET).hex: $(TARGET).elf
        $(OBJCOPY) -O binary -R .eeprom $(TARGET).elf $(TARGET).bin
        $(OBJCOPY) -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 $(TARGET).elf $(TARGET).eep
        $(OBJCOPY) -O ihex -R .eeprom $< $@

$(TARGET).elf: $(OBJS) core.a
        $(LD) $(LDFLAGS) $^ -o $@

core.a: $(COREOBJS)
        $(AR) rcs $@ $^

%.o: %.S
        $(CC) $(ASMFLAGS) -c $< -o $@

%.o: %.cpp
        $(CC) $(CFLAGS) -c $< -o $@

# fuse7 = APPEND
# fuse8 = BOOTEND
upload: $(TARGET).hex
        $(AVRDUDE) $(AVRDUDE_FLAGS) \
                -Ufuse0:w:0b00000000:m \
                -Ufuse2:w:0x02:m \
                -Ufuse5:w:0b11000101:m \
                -Ufuse6:w:0x04:m \
                -Ufuse7:w:0x78:m \
                -Ufuse8:w:0x00:m \
                -Uflash:w:$(TARGET).hex:i

clean:
        rm -f $(OBJS) $(TARGET).elf $(TARGET).hex core.a $(COREOBJS)

You can see the fuse configuration near the bottom in the upload target.

Unreal-Dan commented 1 year ago

I realize this isn't exactly related to megatinycore per-se, since I'm trying to do something outside of the library intended functionality, but I'm not sure who else I can ask that would have intimate knowledge on this subject.

If I can somehow prepare a patch to give back to megatinycore afterward I'll try to do that.

Unreal-Dan commented 1 year ago

I've produced a minimall reproducible example that uses some ripped out code of megaTinyNeoPixel along with the main function ripped out of megatinycore:

https://github.com/StoneOrbits/VortexEngine/blob/daniel/finger/avr_gcc/appmain.cpp

about 170 lines it sets the led color based on whether it was able to write then read back the saved data.

Unreal-Dan commented 1 year ago

Okay so I tracked down the issue, for whatever reason the APPEND fuse doesn't seem to work, I can't put my code entirely in the append and write to app data -- it gives a write error.

However, I am able to change bootend if I strip away two calls from megatinycore: onBeforeInit() and initVariant().

I guess having calls to those two functions causes the linker to include some of the other blackmagic from those files which results in the BOOTEND fuse being unable to be changed.

But once I can change the bootend fuse I am able to write beyond the end of the boot section as expected.

Perhaps the data sheet is wrong when they say app code can write app data, perhaps only bootloader section can write appcode/appdata.

Anyway, if you can help suggest a way for me to incorporate this into megatinycore I'd be happy to do the heavy lifting and provide you a merge request -- that being said you did mention this is a very minimally used feature so I'm not really bothered if you don't want to include it.

Thanks for all the input you gave up till here, let me know if you want me to prepare a patch and what exactly I should cover in that patch.

Thanks again

Daniel

SpenceKonde commented 1 year ago

There is one thing that I just thought of! Could it be.... that when you had APPEND set to a non-zero value, you had BOOTEND set to zero? Per datasheet, whenever BOOTEND/BOOTSIZE is 0, all of the flash is considered bootloader section

What's that, you think that's moronic? So do I! Nobody will want to write app data from appcode if not using a bootloader? Terrible design assumption!

To make APPCODE write to APPDATA but not use a bootloader you need to do:

  1. Ensure that BOOTEND or BOOTSIZE (depending on the part, both names have been used - probably because someone pointed out what a terrible choice APPEND was, and so they replaced them with BOOTSIZE and CODESIZE. is non-0. 1 is the value you want here.
  2. Before interrupts are enabled, (meaning earlier in the boot process than we normally run user code, you must kick the CPUINT interrupt controller. This must be done before setup is called. That might prove awkward on some cores; we provide nominally empty onPreMain() callback that you can override. It is called in .init3, after __zero_reg__ and the stack pointer are set up, but before ANYTING else . There's no millis/micros yet. Calls to delay will hang for eternity (note that the same is not true of delayMicroseconds() - however the times will be wrong because we don't set the clock speed until after all the .init sections run and we've .
  3. APPEND/CODESIZE must be smaller than the flash.
  4. The application code must fit within APPEND or CODESIZE.
void onPreMain() {
        _PROTECTED_WRITE(CPUINT_CTRLA,CPUINT_IVSEL_bm);
}

This will, very early in the boot process, before we execute sei, tell the CPU INT controller than the vectors start at 0x00, not the end of the fake bootloader section that we're creating just so that we can have a fucking appcode section.

I am 99% sure that what I've described explains your problem, and that doing those things would make APPCODE writes to APPDATA work fine. Does this bring things into focus?

I think the only patch that would really be super helpful is to go whole hog and implement the same API as Flash.h does, because without that. there's no point in any other appcode -> appdata write related functionality, and I really don't want to try to reimplement it for writes from the app section because I can't read the code because I don't really know C++ very well. All the MCUDude libraries are balls-to-the wall C++ and they make my head spin - I don't know C++. I'm passable at writing C, and I like to think I'm pretty good at AVR asm (I have actually written snippets of code in asm, because I considered the ASM to be easy while implementing it in C, I couldn't think of how to do), but we're locked into that API. (Don't give me any credit or blame for that API- I didn't touch the library, I did less to that library than I did to any other library that I've merged - that's @MCUdude's work - the Flash.h in DxCore with the completely different API - that is my work :-P I predict that anyone who reads both readmes will think that one did it all wrong and the other did it very well, I'm not sure whose would win the vote though. In any event a library like what I proposed would also cover the ATmega4809/4808 series (they have identical nvmctrl), so you could get it added to MegaCoreX too if you could convince @MCUdude to add the menu (good luck - he hates tools submenus, while I love them. Him and I have the same vision - "An Arduino core for Every AVR" but beyond that.... the chances of us agreeing on what the right way to implement something is about the same as the chance of both of us winning the lottery on the same day. I don't play the lotto, and he doesn't strike me as a gambler either so the chance is low indeed. I can only think of one time where I wanted to change a library and he thought my change was a great idea, and he thinks I made the wrong design decisions at almost every turn in my core. I feel the same way about his. If you notice, his core has like none of the cool features of my cores. That's because he doesn't like them and/or doesn't like how I committed them. He knows how to use git, so where it will take me like a dozen commits over several days to get a feature in, he knows incantations that would let that all go into a single commit so he could read the list of commits and see what features I added. He once told me how he'd expect me to do it. Maybe if there were 48 hours in a day and my IQ was 48 points higher I'd be able to do it that way - but all I could get out of his description is "He ain't never gonna like my code because to make him like it, I would have to have a completely different development methodology". I only the other hand can't follow the changes he puts into his core either. Because I place the list of changes into a file called changelog.md, not the commit history, and consider that the authoritative resource on changes. While last I checked, that's not how he rolls. So neither of us can see eachothers changes in the format our brain natively understands.

He may very well be right and I may very well be wrong. In fact, if we assume that his education at the college level is something other that a waste of 4 years and a hundred grand like mine was (I am pretty sure his major was CS or ECE or something. ie something useful in programming), and that you do become more competent in a subject area from studying it formally, it's near certain that he's the one who is in the right more often than not. I'm just a dumbclunk with a masters degree in.... chemical fucking engineering focused on fuel cells (when I realized that every guest speakers pitch that had numbers in it if worked out, would require annual production of platinum to exceed that whih has been produced by humanity in total since the dawn of time, I should have gotten the message).

I've never used that education at all (all the things I want to chemically engineer would... I don't know how else to put this... involve committing violations of local, state and federal laws, international treaties, and usually all of the above *). I would go insane trying to work for a ChE company because the culture at such companies is so stiff you'd think their staff was dead and experiencing rigor mortis, and besides they all seem to have a 8am to 4pm or 9am to 5pm schedule. That's closer to when I sleep than when I'm awake, and the part of the day I am guaranteed to not be productive. I'd probably be fired in short order because I'd be late to work every day and would be unproductive because my body would want to be asleep. So after I got my Masters degree, I realized my ChE career choices would be racking up pinkslips until I was unable to get a job at McDonalds because my record would be so bad, or career criminal, neither of which seemed appealing. And I graduated in 2009 so ChE couldn't get jobs anyway - all the in's I had for ChE jobs, if the company didn't go under, they fired most of their ChE's so the job market was flooded with more experienced ChE's and nobody would give a new grad the time of day when tyhey had people with decades of experience applying for the same positions anyway. Connections got me a job in software test which I kept for 10 years until the stress of being evicted when my landlord, Dr. Jeckel, turned into Mr. Hyde. Our lawyer (he was threatening a lawsuit and things got so heated that we couldn't communicate with him directly. Wanted to compel us to pay rent for the rest of the lease even though we werent allowed to live there plus $10k of damage beyond what our damage deposits covered. Now granted I admit that the second largest line item on the damage list, that I'd trashed the floor in my bedroom with my office chairs, yeah that was my bad. (I didn't know until afterwards that you can get replacement wheels that don't trash the floor), The biggest line item though was the bathtub, which he hired some clowns to come do liner on, without fixing the original problem (that it was only a shower because if we closed the drain water poured out of the ceiling downstairs) and the liner disintegrated almost immediately, and he blamed us for for that. I should have picked up on what an asshole he was earlier, maybe when he got into a fight with the heating repair guy once who then came back and disabled the gas feed into the building on a friday night and they couldn't fix it until monday, because it had to be done by the gas company only - it was a safety lockout that he intentionally triggered. I think because my landlord caught and got him in hot water because he hadn't pulled the legally required permit.) Anyway, yeah our lawyer said he was one of the hardest people to negotiate with they'd encountered in 30 years of practice (in housing law, ie, she spent those 30 years working with lawyers and landlords, so that's a really high bar). Oh, and he stole a shitload of my tools when he abruptly changed the terms of departure. He said he called in a junk removal company and everything went to them. You think the painters light (he sure needed one, judging by the paint job in that place), the beautiful made in america corded drill, the complete luxury dremel with all the accessories, the sawzall, my car battery charger - you think he let the junk removal people take those? My ass he did. I just hope the other unfortunates renting from that deranged crook at least are getting competent paint jobs now thanks to the painters light it jacked. But the whole situation was so stressful and upsetting that I was unable to fulfil my work responsibilities and was fired ("with cause" - no unemployment). I hated that job anyway. In the past 5 years it had gone from a nice place to work to like full on 100% dilbert.

I really gotta get patreon and github sponsors set up (and hope some people sponsor me!) and get these products listed cause I gotta bring in more money. I'm not making 1/5th of what I need to. And the thought of working for someone else - no, last time I did that it was so traumatic and stressful it was ruining me, and I cannot imagine subjecting myself to that again, and maintaining these cores takes enough time that I couldn't do a day job and have maintain cores anyway. and I don't know of anyone in the community who I would feel confident passing the projects off to. (I literally spend like almost all the time I'm not eating or sleeping working on electronics shit)

**AAAANYWAY*** Were you to submit such a library, your code will be be merged in something like this:

#if defined(SPM_FROM_APP) && SPM_FROM_APP && !defined(USING_OPTIBOOT)
/* Your new version of Flash.cpp */
#elif defined(USING_OPTIBOOT)
/* current content of Flash.cpp */
#else 
  #error "In order to enable app writes to SPM when not using Optiboot you must explicitly enable them"
#endif         

and there would be no need for the onPreMain override because I'd just test for those things in main.cpp where onPreMain() is called from like I do on DxCore. In the ideal case the library would not need to add #if's to the header; I think that's unrealistic but that would be what we want to be close to. If you provided the library code, I'd add through a menu a way to set APPEND, and it would compile the library with SPM_FROM_APP = APPEND; default would be disabled (SPM_FROM_APP not defined) and then work it's way up from very small to large app sections and I'd have core_device.h #error if the selected APPEND is beyond the end of the flash.

Now - one argument one can have is should maximum upload size be adjusted to match, ie, so it would error if you try to compile a sketch and theresult spills out of APPCODE and into APPDATA? On DxCore it's not, so if you go over CODESIZE with your app, you are in undefined behavior land. It would be nightmarishly hard to make boards.txt do that I think- but that would be my problem. The thing that's keeping this from appearing in the core is the lack of code that can write to APPDATA from APPCODE.

* Yeah it'd be straight up illegal worldwide, but goddamn I would like more Chlordane. Yes, it made sense to ban people from spraying that stuff willy nilly as an insecticide. But if you target it carefully, miniscule quantities can keep a crack impassible to pestiferous insects for decades. The cracks that my father treated in his basement floor when i was in early elementary school? No ant has come in through them since. If you wantedto dfo that with non-banned insecticides, it would be like 5-10 applications every year. Is that really more ecologically responsible? By the end of 40 years you'd have emptied several whole cans of Raid r equivalent to do what could have been done with a single milliliter of banned insecticides applied one. I'd be happy with some DDT instead, it'd be just as effective at least ("A mosquito was heard to complain / that chemists had poisoned his brain! /The cause of his sorrow/was para-di-chloro/di-phenyl trichloro ethane" - from the better living through chemistry era. Another nice poem from then: "Oil and ointments, wax and wine/ and the wonderful colors called analine / You can make anything from a salve to a star / if you only know how - from black coal tar"). But I digress

Unreal-Dan commented 1 year ago

Honestly that's one long post and it's going to take me a little bit of time to get through it all, I really appreciate the time you've taken to help me.

You can find more information about my problem and the course to solving it here:

https://www.avrfreaks.net/s/topic/a5C3l0000003jtrEAA/t390218

You're right actually, it is the fact that I was trying to use appcode -> appdata without a bootend value which isn't allowed as per one single line in the data sheet.

The core problem though was when I changed my bootend to a non-zero value while using megatinycore my program wouldn't start, it's quite possibly you've outlined the solution to this in your post above so apologies if I haven't gotten to that part yet.

At this point in time I've shedded megatinycore from my project because every byte of space is very important, but I am definitely indebt to you for the solutions you've provided that have made my life a lot easier.

I would really like if we could get in contact in a chat/instant messaging context, it would make communication of the above information a lot easier and I could possibly even help you deobfuscate the awful c++ code from mcudude.

I'll try to update this as I read more of your post, till then thanks again

SpenceKonde commented 1 year ago

Yeah sorry bout that, my response length dials's detents in the middle are worn, so if it's near the short end you get replies like this, if it'sat the other side you get... responses like above. But it doesn't stay in te middle!

I don't know what chat tools to use for this - I'm used to doing zoom meetings from pandemic and still have paid account, but could definitely do that - afternoon to after midnight most any day is available (to people developing code for one of my cores I mean if interested in that. I like zoom cause it also lets you screenshare, which is super helpful with code.

SpenceKonde commented 1 year ago

And to be clear I'm not kidding when I don't know what chat tools people use now. All my friends used to use slack, until mad drama went down, now we use signal. and obv. I know zoom. I don't know any tools for realtime text or voice chat other than those.

Unreal-Dan commented 1 year ago

discord is great, it's like modern day IRC if that means anything. You have servers you can join (or create your own, free hosted) and channels on those servers. You can directly add friends and message them too.

the users are name#number you can add me Daniel#8135

You will find many communities use discord I think it's great, zoom is not the best.

Cheers

SpenceKonde commented 1 year ago

Well I've created an account

SpenceKonde commented 11 months ago

So it sounds like this was sounds like this was a simple case of APPEND-icitis. It's a temporary reaction to some of the less obvious behavior of the modern AVR NVMCTRL, very common, almost everyone gets it upon first exposure, but it can be treated by fairly simple steps entirely in software.

APPEND was not a very good name for the symbol at the end of the application for a variety of reason, probably most significantly that it is spelled the same as the CS jargon word "append". That's why BOOTEND and APPEND were changed to BOOTSIZE and CODESIZE in Dx. Identical meanings and behavior (well, except for errata, there's a minor and easily worked around one on the DA. And at least three much nastier ones on the EA48's

Anyway, at that giant post near the top are four numbered steps. That's the important part of the post, which tells you what's needed to ensure that something like that works.

And I definitely agree with your complaint about the small EEPROMs on these parts - though I often just use 24-series EEPROMS if I need extra space.

(Aren't you glad capitalization and punctuation exist though? Otherwise the next guy would have read my diagnosis and started prepping you for surgery. We could be wheeling you around on a stretcher "Dr. Azzy said it has to come out!" "I had a problem with flash write, not a medical problem" "He said it comes out so out it comes!" "Uh, is a CNC mill the right tool for this? Don't they use a scalple?" "Maybe some do, but here we use more efficient methods." "Ummm, are you sure this is a good idea?" "Absolutely, we've had not a single complaint!" "Really? What has the mortality been?" "Perfect, 100%!"

"WHAT happened to him officer? A human-shaped CNC mill?! with restraints on it? Where did that nutcase even find something like that...." "Probably the same place he bought the poison ivy houseplant you have your arm up against" "Ghaa?! Potted poison ivy?!" "Yeah, basically everything in the building seems to pose some hazard Even the yard. those beehives you saw on the way in? killer bees..." sniff "Say, officer, do you smell gas?" (scene fades to the Hazmart logo as a muffled explosion noise can be heard). Brought to you by Hazmart - something for everyone on your "list")