ATmega1280PV: A Three-Byte Program Counter Changes Everything
An 8-bit microcontroller with 128 KB of flash has a problem: 128 KB is 65,536 words, and a 16-bit program counter can address exactly 65,536 words. There is no room left for the next instruction.
The solution is a third byte on the program counter. It works, it is invisible to most code, and it leaves fingerprints all over a disassembly that are routinely mistaken for corruption.
What changes at the boundary
Three things, and each one shows up in an image.
The return address on the stack becomes three bytes. A call pushes three bytes instead of two. Code that manipulates the stack directly — a context switch, a hand-written interrupt prologue, a bootloader that relocates itself — has to know this. An image disassembled assuming two-byte returns will show a stack that appears to be drifting.
Long jumps need a register that holds the high byte. On smaller members the jump target fits in the instruction. Here the target’s upper bits come from a register written before the jump. A JMP therefore has a visible predecessor instruction, and the pair must be read together.
Constant data in flash needs a pointer with a high byte. Tables addressed through a 16-bit pointer cannot reach the top half of the array. The compiler inserts a load of the high byte before the table access, which in a disassembly appears as an otherwise unmotivated write.
Why the vector table looks strange
Interrupt vectors are jump instructions, and on this part the unconditional jump is a two-word instruction whose target is split: low bits in the instruction, high bits from the register.
That means a vector table where every entry is two words plus a preceding register load. In a naive disassembly the table looks padded, or looks like it contains data rather than code, because the register load before each jump reads as an unrelated instruction.
Recognising the pattern is the whole trick. Once the shape is known — load high byte, jump — the table becomes readable and the section boundaries become obvious.
The V suffix and the P suffix together
PV combines two independent things: the low-voltage grade and the picoPower die. Neither changes the program counter, the address space, or the file format.
What they change is the electrical envelope during a read. A 128 KB array read at a marginal supply or at an excessive clock rate produces pages that fail intermittently, and on a part this large the read takes long enough that a supply sag can occur midway. The symptom is a file that is correct at the start and wrong at the end.
The defence is structural: read in passes, verify each page twice, and re-read the first page at the end. If the first page still matches, the supply held; if it does not, the whole file is suspect regardless of how plausible it looks.
Interpreting a large image
With 128 KB there is enough space for structure that a small part cannot hold: multiple tables, a bootloader, a string section, sometimes an unused region between the application and the boot section.
| Region | What to look for |
|---|---|
| Vectors | Repeated load-then-jump pairs |
| Start-up | Bus and clock configuration, early in the image |
| Application | The bulk; look for string clusters to orient |
| Gap | FF between application end and boot start |
| Boot section | Its own vector-like entry, often with a protocol implementation |
The gap is informative. A gap that is exactly the boundary between the application region and the boot region confirms the boot size fuse. A gap that is somewhere else suggests the application does not fill its space, which means the build was for a larger part than the one in hand — a common situation with a family that shares a die.
What a recovered image cannot tell you
The image says nothing about the fuses. On a part where the boot reset fuse redirects the reset vector, the image alone gives a misleading picture: the code at address zero may be perfectly good and still never execute.
It also says nothing about the external memory interface. If the bus is enabled and code lives in an external device, the internal image is a fragment — and on this family the bus is present, so that possibility is real rather than theoretical.
Both are answered by reading the fuse byte, which is one command and takes a second. Doing it first is not caution, it is the difference between interpreting an image and guessing at one.
Related notes
- ATmega1280P and the EEPROM array — the separate memory this part also carries.
- ATmega1281PV and reconstruction — the sibling without an external bus.
- ATmega640 and external code — why an image can be a fragment.
FAQ
Does code compiled for a 64 KB part run on this one? Usually yes. The wider program counter is transparent to code that does not depend on address size.
Why do returns look wrong in my disassembly? Because they push three bytes. A two-byte assumption shifts every subsequent stack reference.
Can the whole 128 KB be executed? Yes. The address space is fully populated on this member, unlike a smaller part in the same family occupying a subset.
Does the V grade limit clock speed? At the low end of the supply range, yes. That interaction is what makes long reads riskier on this part.
Conclusion
The third program counter byte is the single fact that changes how a large AVR image must be read. It shows up in return addresses, in jump instruction pairs, and in table accesses, and each of those looks like an error if the assumption is wrong. Knowing the shape turns the vector table from noise into the most structured region of the file.