PostHeaderIcon 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

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.

Comments are closed.