PostHeaderIcon Break MCU PIC16F870 Binary: When the Part Number on the Paperwork Is Wrong

The part number in the email is not always the part number on the board. Within a family, several members share a package, a pinout and most of a datasheet, and the difference between them is a peripheral that one has and the other does not. A customer reading a marking from memory, or from a purchasing record rather than the unit in hand, will name a different member with complete confidence.

The PIC16F870 is the member of its family without the ten-bit converter. Its siblings in the same twenty-eight-pin package have one. That single difference is enough to make the image look wrong to a customer expecting the other part, and it is also enough to prove which device is in the socket.

What the family shares and what it does not

Feature shared across the family differs by member
package and pinout yes, twenty-eight pins no
program memory size varies 2K and 4K members
data EEPROM 128 bytes on most no
ten-bit converter no present on some members only
serial port yes yes
timers three no

Because the package is identical, a board designed for one member will accept another, and a substitute fitted during a repair will work if the design does not use the missing peripheral. That is exactly the case that produces a mismatched delivery: the customer’s records say one part, the board carries another, and both are true at different times.

How the image identifies the device

Three observations settle it, and none requires the marking to be legible.

The converter registers. If the code writes to the converter’s control registers during initialisation, the device has a converter. If it never touches them and the product still measures something, the measurement is being done another way — a comparator with a timed charge, or an external converter on the serial port.

The initialisation footprint. The block that configures the peripherals is a list of register writes, and its contents are a parts list: timers, serial port, converter, comparator, reference. A device without a converter has a shorter list, and the absence is as diagnostic as the presence.

The channel count. A design that scans four analogue channels has four entries in its sampling routine. A design with none has no sampling routine at all, and what looked like one is a comparator threshold check.

Together these give an answer that is independent of any label.

Why the distinction changes the analysis

On a part with a converter, the reading of the program is dominated by the analogue path: sampling order, reference selection, scaling arithmetic, filtering. That is where the product’s behaviour is defined.

On a part without one, the same functional block is implemented differently — usually as a comparator plus a timer, measuring the time for a capacitor to charge through a resistance. The code is a timing loop, and the constant that matters is the count, not a scaling factor.

A reader who expects one and finds the other will describe the second as a broken version of the first. That misreading is the most common analytical error on this family, and it starts with the part number.

The marking, when it can be read

Top markings on parts of this vintage are sometimes legible and sometimes not. When they are, the family code and the memory code together identify the member; the temperature and package suffixes follow. When they are not, the die marking after decapsulation gives the same information more reliably, along with a mask revision that the package marking does not carry.

It is worth photographing the marking before any work begins, because a part that is later decapsulated has no marking left to argue about.

The conversation to have before the work

Situation What the customer believes What the image shows What to do
correct part, converter unused the design measures comparator timing explain, then annotate accordingly
wrong member named a converter design no converter present correct the record, proceed
substitute fitted in repair original part a different member establish which behaviour is required
unmarked part unknown identified from the initialisation report the identification

The third row is the one with commercial consequences. A unit repaired with a substitute may have been working in a reduced mode for years, and the customer’s expectation of “the same as before” refers to the substitute’s behaviour, not the original’s.

Delivering with the identification stated

The delivery should open with the identification: which member of the family the image came from, how that was established, and what it implies about the peripherals in use. That paragraph prevents every subsequent misunderstanding, and it costs nothing to write.

Then the image over the full range with the word convention, the configuration word decoded, the data EEPROM raw, and a note on the analogue path — converter or comparator — with the constants that define its behaviour.

Where the sibling with the converter requires the sampling analysis, PIC16F872 program covers the reachability of that path. Where a forty-pin part shows what the extra pins are for, PIC16C662 heximal reads the ports. And where the identifying evidence has to be gathered before any interface is touched, CF755 dump recovery sets out that order.

The short version

Three members, one package, one difference that matters. Identify the device from the register writes rather than from the paperwork, state the identification at the top of the report, and read the analogue path according to what is actually there.

PostHeaderIcon ATmega261PV: A Device That Is Asleep When You Connect

A battery product spends most of its life asleep. The firmware wakes on a timer or an edge, does something brief, and returns to the deepest mode the part supports. Current drops to microamps and the core stops.

That is correct behaviour and it makes bench work confusing, because a sleeping part does not execute, does not drive pins, and does not necessarily respond to anything you connect. Two measurement habits resolve the confusion, and one fuse setting explains it.

What sleep actually stops

The modes form a ladder. Each removes more of the chip:

Mode Core Main oscillator Peripherals Typical wake source
Idle stopped running running any interrupt
Power-down stopped stopped watchdog, some external edge, watchdog
Power-save stopped stopped, timer kept asynchronous timer timer, external edge
Standby stopped running, fast start most off external edge

The deeper the mode, the longer the wake and the fewer things that can cause it. On a firmware that measures once a minute, the part spends 99.9 percent of its time in the bottom rows.

Why this looks like a dead part

Three observations that mislead:

Supply current near zero. A part in deep sleep draws less than the leakage of a careless probe. A current measurement that reads “nothing” is consistent with both a sleeping part and a disconnected one.

No activity on any pin. Sleeping firmware leaves pins in whatever state it set before sleeping. If those states are static, a scope shows flat lines everywhere.

No response to the programmer. Entry into programming mode requires the core to respond. A part that is asleep may need the reset line asserted, and some adapters do not assert it long enough.

The distinguishing test is to measure the reset line while the programmer attempts entry, and to look for the periodic blip that a timer-driven wake produces. A device that wakes once a second is visible on a current trace even when it looks inert.

The fuse that governs the wake

The watchdog can be configured to run in the deepest sleep modes, and it is the usual wake source for a product that samples on an interval. If the watchdog is enabled unconditionally, the part will wake on its own schedule regardless of what the firmware wanted.

That is the same fuse discussed elsewhere, and here it has a second meaning: it is the mechanism that lets a deeply sleeping device do anything at all. A design that sleeps to microamps and wakes on a watchdog is a design with almost no external components, which is why the pattern is so common.

Reading the sleep structure out of the image

The power management code is compact and recognisable:

  • Writes to the sleep mode selection field.
  • A sleep instruction, usually inside a loop.
  • The interrupt that wakes it, which must be enabled before the sleep.
  • The work done after waking, which is usually short.

Finding that loop gives the product’s duty cycle: how long it sleeps, what wakes it, and how much it does per wake. On a battery product, that ratio is the design.

Code feature What it tells you
Sleep mode field value How deep the part goes
Wake interrupt source What the product reacts to
Work after wake What it measures or transmits
Loop structure Whether timing is timer-driven or event-driven

What this means for access

A device that sleeps is not protected; it is idle. Access is a matter of holding the reset line long enough for entry, which is a procedural fix rather than a barrier.

Once in, everything proceeds normally: signature, fuses, lock byte, array. The only difference from a device that runs continuously is that the programmer may need to assert reset more firmly.

That distinction matters because “the device does not respond” and “the device is protected” are frequently confused, and the first is far more common on battery products.

Related notes

FAQ

Does sleep affect the flash array? No. Sleep stops the core and clocks; memory is untouched.

Can a sleeping part be programmed? Yes, when the reset line is asserted properly. Programming mode does not need the application running.

Why does the current reading look like zero? Because the part is drawing microamps. A probe and a meter with adequate resolution will show the difference.

Does the watchdog wake the part in every sleep mode? In the modes where it is clocked, which is why it is the usual choice for interval wake.

Conclusion

Deep sleep is a design decision that produces a device which looks inert and is not. The current trace, the reset line, and the sleep loop in the image together describe the duty cycle — and on a battery product that duty cycle is the most important thing the firmware does.

PostHeaderIcon Recovery Microcontroller PIC16F872 Program: Reachability Is the Program

Two kilobytes of words looks like a manageable image until you notice that not all of it is program. Some of it is constant data that the instruction stream reads but never executes, some is the residue of an earlier revision that nobody deleted, and some — on a part with computed jumps — is code that only a table can reach. Telling those apart is what turns a dump into a program.

The PIC16F872 is a twenty-eight-pin flash part with 2048 by 14 bits of program memory, 128 bytes of data EEPROM, and a ten-bit converter with five channels. It is the model of a mid-range controller from the era when a design might run for fifteen years and accumulate four firmware revisions, each leaving something behind.

The reachability graph

The analysis starts at word zero and follows every control transfer.

Transfer type How to follow it Risk
call or goto with an absolute target direct none
conditional skip followed by a jump both paths none
computed jump through a table requires resolving the table an unresolved entry looks unreachable
jump through a modified program counter requires data-flow analysis the usual source of error
return from a table returns to the caller, not a new block misread as an entry point

The third and fourth rows are where naive tools fail. On this core, an indexed jump is done by adding an offset to the program counter register, which means the target is computed from a value that may have been loaded several instructions earlier. A disassembler that does not track that value treats the jump as falling through, and the entire block it reaches is then marked unreachable.

Three kinds of unreachable code

Once the graph is built, the unvisited regions fall into recognisable classes, and each means something different.

Removed features. A block that begins with a test of a flag nobody sets, followed by a complete routine. It was a feature that was disabled rather than deleted, usually because the customer might want it back.

Superseded revisions. Two routines doing the same thing, one called and one not. The dead one is the earlier attempt; the live one is the fix. Comparing them often reveals what the bug was.

Unresolved computed entries. A block reached only by a table whose entries the analysis did not resolve. This is not dead code, it is a failure of the analysis, and it has to be closed before the other two classes can be trusted.

The ordering matters. Classify the third first, because every unresolved entry contaminates the classification of everything it reaches.

Resolving computed jumps

Three patterns cover almost all of them on this part.

A table of retlw instructions read through a call, with the value used as data or as an offset into a second table. The first table is constants; the second is a jump table.

A block that loads a literal into the working register, adds an offset, and moves the result into the program counter. The added offset is a base address, and the resulting target is a small range of addresses immediately after the addition.

A state machine dispatch, where the state variable indexes a table of goto instructions. This one is easy to resolve because the state variable is written with small literals in a small number of places.

Each of these has a signature in the listing, and once they have been resolved the graph becomes dense: on a typical design of this vintage, fewer than one word in twenty remains unreachable.

Why the converter matters for the reading

Five channels of ten-bit conversion is the reason this part was chosen for sensor products, and the conversion logic is a reliable guide to what the code is doing.

The channel selection written at initialisation states how many sensors the product has and which pins they use. The sampling routine — a delay after channel selection, then a conversion start, then a wait — appears once and is called from everywhere, so it is a good anchor for identifying the code’s organisation.

The scaling arithmetic after the conversion is where the product’s range lives, and it is usually a shift-and-add sequence with one or two literals. Reading those literals gives the counts-to-units conversion without needing the sensor’s datasheet.

Where the graph goes wrong

Three failure modes are worth checking against any reachability result before it is published.

A jump into the middle of an instruction. On a core where instructions are one word each this is possible if the target is computed from a value that was not what the analysis assumed. The symptom is a block that decodes into instructions which reference registers the design never uses.

A table whose entries point outside the array. That is either a transcription error in the table or a dispatch that the analysis resolved with the wrong base address, and both are worth resolving rather than classifying as unreachable.

A region that reads as code but is never targeted and contains no return. Functions end with a return; data does not. A run of instructions with no return at the end is either a block that falls through into the next one — which means it is not a separate function — or a block whose terminator was misread.

Each of those is cheap to check and each of them, left unchecked, turns into a wrong statement in a delivery.

Delivering a program, not an image

Artefact Content Who uses it
image full array, stated convention production
call graph every routine with its callers engineering
reachability map reachable, residue, unresolved engineering
channel and scaling note inputs, ranges, units systems
configuration decoded production and engineering

The third item is the one that makes the second trustworthy. A call graph without a reachability statement presents dead code as if it were live, and an engineer who changes a routine that is no longer called will wonder why nothing happened.

Where a forty-pin part in the same family shows how the extra pins are used, PIC16C662 heximal reads the port structure. Where the sibling without the converter requires a different reading, PIC16F870 binary covers the case. Where units of one design from different production years disagree, PIC16F871 software explains why the difference is usually revision rather than damage. And where the file packing rather than the graph is the risk, PIC16C642 binary deals with offsets.

The short version

The program is the reachable part. Build the graph from word zero, resolve the computed jumps first, classify what remains as feature-removed or revision-residue, and deliver the map alongside the image so nobody edits dead code.

PostHeaderIcon Break IC R5F562T7ADFF Binary: The Version String Is the Most Valuable Thing in the Image

The first question after a read is almost never “what does the code do”. It is “which one is this”. A customer with twelve units in the field, three hardware revisions and four firmware releases needs to know which combination is in their hand before anything else in the delivery matters, because the answer decides whether they are looking at a known defect or an unknown one.

That answer is usually sitting in the image as plain text. Products embed version strings for their own support reasons — a boot banner, a service-menu display, a diagnostic response — and those strings survive compilation intact. Extracting one takes minutes and it changes the value of the whole engagement.

The part is R5F562T7ADFF#V1: RX62T group, thirty-two-bit core with an FPU, two twelve-bit converter units and the motor-control timer set. Note that the version suffix on the order code identifies a shipping configuration and has nothing to do with the firmware revision. Confusing the two is a common and expensive mistake at the quotation stage.

Four places a version hides

location typical form how reliable
a string constant near the start ASCII, often with a date very high
a build banner printed at power-up ASCII, longer, may include a compiler name very high
a packed numeric constant two bytes for major and minor, a word for build moderate, needs context
a checksum stored beside a table the same word repeated in two places low, confirm by computation

The first two are the ones worth searching for first, because a text search needs no addresses, no endianness and no architecture knowledge.

Searching a binary for text

A flat binary has no structure, and that is an advantage here: scan for runs of printable ASCII of length six or more, and read what comes out.

Three patterns repay the effort. A version string in the form word-major-minor, often preceded by a product name. A date in one of the compiler’s formats, which is frequently the build date and is the most decisive single identifier because it orders revisions absolutely. And a compiler identification string, which is less useful for the customer and extremely useful for the rebuild work that may follow.

The scan finds noise as well: error message text, unit names, protocol keywords. Sorting the candidates by whether they contain a digit and a separator character, and by their position near the start of the image, reduces a few hundred hits to a handful.

When there is no string

Four fallbacks, in order of effort.

The reset path. Some designs compare a stored value against a computed one at startup and branch to a fault handler; the constant used in that comparison is often a build identifier, and the comparison itself is a strong enough landmark to find.

The table layout. Different revisions place constant tables at slightly different addresses, because tables grow. The base address of the largest table is therefore a rough revision indicator, useful for distinguishing two known revisions and useless in general.

The code structure. A revision that fixed a bug has one function that is slightly larger or slightly different from its predecessor. Comparing two recovered images function by function localises the difference and identifies both.

And the customer’s own records. A serial number in the option area or in an ID location, matched against a production database, identifies the revision without any analysis at all — which is why asking for the serial number early is worth the email.

Why this matters more than the part number

The part number identifies the silicon. The customer already knows the silicon; they have the board. What they do not know is which firmware build is on it, because the label on the enclosure refers to the product, not to the code, and the code has been updated in the field more often than the label has.

A delivery that states the revision up front is a delivery the customer can act on immediately. One that states only the model and the array length forces them to compare images themselves, which is exactly the work they were trying to avoid.

Putting it in the report

One line, at the top, before any technical content: the product revision identified, the evidence used, and the confidence. Where the identification rests on a string, say so and quote it. Where it rests on a comparison against a reference image the customer supplied, say that instead, because the confidence is different.

Then the second line: what the image does not tell you. A version string identifies a build, not a configuration; the same build can run with different parameter sets, and the parameters live in the data region rather than in the code.

For where the data region sits, R5F562T7ADFF EEPROM covers the location markers. For the flat-file properties that make this search necessary, R5F562T6BDFF binary deals with base and length. And for the order-code suffix that is so often mistaken for a firmware revision, R5F562T7DDFF code sets the record straight.

FAQ

Is the version string always in plain text? No. Designs that compress their strings or strip them for size have none, and then the identification is a comparison exercise.

Can a version string be wrong? It can be stale, if somebody updated the code without updating the constant. That is rare and it is detectable when the string disagrees with the code structure.

Does the order code tell you the firmware revision? No. It identifies the device and its shipping configuration. The firmware revision is a property of what was programmed into it.

The short version

Before analysing what the code does, establish which code it is. Scan for printable strings, keep the ones with digits and separators, and look for a build date — that single line answers the customer’s real question and it is the cheapest useful output of a whole recovery. Then say what the string does not cover, so nobody assumes a build identifier is a configuration identifier.

PostHeaderIcon ATmega261P: When the Watchdog Is Always On

A watchdog exists to recover a device from a firmware hang: a timer runs independently, and if the firmware does not reset it in time, the part restarts. Normally the firmware decides whether to enable it.

One fuse removes that decision. With it programmed, the watchdog is on from reset and cannot be turned off in software. That is a sensible choice for a product that must never hang, and an awkward one for anyone trying to work with the device on a bench.

What it changes at the socket

The part resets itself at a fixed interval, which begins at power-on and cannot be disabled. Every operation the programmer performs has to complete inside that window, or be resilient to being interrupted by a reset.

Two observable consequences:

Observation Cause Not to be confused with
Read stops partway Timeout exceeded mid-transfer A damaged page
Entry fails outright Core restarted during entry A dead die
Erratic success rate Depends on how fast entry completes Contact problems
Nothing at all Watchdog fires before entry begins A fuse-disabled interface

A read that takes longer than the timeout never completes. The interface returns an error, or returns data that stops partway. Retrying produces the same result, because the timeout is deterministic.

Entering programming mode can fail outright. Some programmers assume the device stays in reset while they take control; a watchdog that fires during that interval restarts the core and drops the connection.

Both look like hardware faults and both are configuration facts.

The window and how to work inside it

Three strategies, in order of preference:

Read in short passes. Break the array read into page groups that complete inside the timeout. Each pass restarts from a known address, so an interruption costs one pass rather than the whole read.

Hold the part in reset externally. If the programming adapter can keep the reset line asserted except during the transfer, the watchdog does not get a chance to fire. This is the cleanest solution and depends on the adapter.

Use the mode that disables the watchdog on entry. Programming mode usually disables it, which is why the failure is intermittent rather than constant — some entry sequences succeed and then the timer restarts.

The third point explains a confusing symptom: a device that works on one programmer and fails on another, with the same fuse setting. The difference is how each tool enters programming mode and whether it does so before the watchdog has had time to fire.

Why the fuse is set

Three reasons, all legitimate:

A product that must never hang — an appliance controller, a safety interlock — enables it deliberately and documents it.

A product that hangs in the field and had the fuse set as a field fix, without a firmware change.

A production line that enables it by default because the template did.

The third is the most common and the least considered, and it produces devices whose behaviour surprises the people who built them.

Reading the fuse byte answers it first

Before any attempt to explain an unstable read, read the fuse byte. The watchdog fuse is one bit, and it is either set or not.

If it is set, the instability has an explanation and the fix is procedural. If it is not set, the instability is electrical or a contact problem, and that is a different investigation with different tools.

This is the same principle that applies to every fuse on the part: the byte describes the configuration, and the configuration explains the behaviour. Reading it first avoids diagnosing a device as faulty when it is doing exactly what it was told.

What the P suffix contributes

The picoPower die lowers current consumption across the board, including in the watchdog oscillator, which on this family runs independently so the watchdog can operate in the deepest sleep modes.

That matters for a battery product and it does not change the mechanism: the fuse still decides whether the watchdog is unconditional, and the timer still runs whether the core is awake or not. A deeply sleeping device with an unconditional watchdog wakes periodically, resets, and sleeps again — visible on a supply current measurement as a periodic blip.

Related notes

FAQ

Can the watchdog fuse be cleared in software? No. It is a fuse, and the point of the setting is that firmware cannot override it.

Does the watchdog run in sleep mode? Yes. That is what makes it useful for recovering a sleeping device.

Why does one programmer work and another fail? Because entry sequences differ, and the window is short.

Does a watchdog reset erase anything? No. It restarts the core; memory is untouched.

Conclusion

An unconditional watchdog is a configuration, not a fault, and it produces symptoms that look like both. Reading the fuse byte before diagnosing turns a mysterious intermittent failure into a procedural constraint — and explains why the same device can behave differently on two programmers that do the same job.

PostHeaderIcon Recovery MCU PIC16C662 Heximal: Reading the Board Back from the Port Writes

Forty pins means a device that spends a good part of its time driving things. On a smaller package the code is dominated by decision logic; here a substantial fraction of the instruction stream is writes to ports, in patterns that repeat at fixed intervals. Those patterns are a description of the board, and reading them is often more informative than reading the arithmetic.

The PIC16C662 is a forty-pin OTP part with 4096 by 14 bits of program memory and the five ports that the package allows. It appears in appliance controllers, instrument front panels and industrial timers — designs where the microcontroller is the whole user interface.

Four port patterns that identify hardware

Pattern in the image External circuit How to confirm
one port written with a changing pattern, another port cycling one bit high multiplexed display the cycle period matches a refresh rate
a port driven with a walking one, another read immediately after keypad matrix the read follows the drive within a few instructions
a single pin toggled at a fixed period with data on another pin clocked serial device the period is a round number of microseconds
a port written once at start-up and never again static output or latch the value encodes the initial state of several outputs

The first row is the most common on a device of this size, and it is also the most rewarding to read, because a multiplexed display carries the product’s entire visible output in the code.

The multiplex, in detail

A multiplexed display works by driving one digit at a time and relying on persistence of vision. The code therefore has a loop that runs at a fixed rate, and inside it, for each digit: write the segment pattern to one port, enable the digit on another, wait a short interval, disable, move to the next digit.

Three facts can be extracted from that loop.

The number of digits is the number of iterations, which is visible as the loop counter’s limit. The segment encoding is the set of values written to the segment port, which is usually a table indexed by the digit value — so the table can be decoded into a seven-segment map, and the order of the table tells you whether the display is common-anode or common-cathode. And the refresh rate is the loop period divided by the digit count, which should land in the range a human eye integrates.

A design with four digits refreshing at a hundred hertz has a loop period of forty milliseconds and a per-digit dwell of ten. That number matching a plausible display is a strong sign the loop has been read correctly.

The segment table is also where character faults become visible. A product that displays a colon, a decimal point or a small set of letters has extra entries in the table, and their bit patterns identify which segments are wired.

The keypad scan

A matrix keypad is read by driving one row at a time and reading the columns. The code is a loop over rows: write a walking-one pattern to the row port, read the column port, and if any bit is low, decode which one.

Element What to look for What it tells you
row drive a value with one bit clear, rotated each pass how many rows, and their order
column read a port read immediately after the drive how many columns
debounce a delay loop inside the branch the switch quality assumed
repeat a counter that re-triggers after a hold whether the product supports auto-repeat

The fourth element is a product decision rather than a hardware fact: a keypad with auto-repeat is a value entry field, one without is a set of function keys. Reading it tells you what kind of interface the product has before you read a single label.

The clocked serial line

When a pin toggles at a fixed period with data changing on another pin, the design is bit-banging a serial protocol rather than using a peripheral. On this part that is common, because the peripheral pins may be needed for the display.

The bit period converted through the oscillator frequency gives the rate, and the rate identifies the protocol family: a few kilohertz is a slow sensor bus, hundreds of kilohertz is a memory interface, and a rate that matches a standard baud divisor is an asynchronous line with a start bit.

Reading the bit order — least significant first or most — is the other half. It shows up as whether the loop shifts left or right, and getting it wrong reverses every byte in the analysis without changing anything else.

Where the port map becomes a schematic

The end product of this reading is a table: pin, direction, function, timing, external device class. On a device with five ports that table has between thirty and forty rows, and it is the closest thing to a schematic that exists for a product whose drawings are lost.

Two cautions about confidence. A pin that is configured as an output and written once may be a latch, a power enable, or a signal to another processor — the code cannot distinguish those. And a pin that is never touched may be unused, or may be an input whose value is never read because the feature was removed, which is common on a part whose firmware went through several revisions.

Where the analysis stays honest, the table distinguishes what is read from what is inferred, and the inferred rows are marked.

Why heximal rather than an image

The plural in the request is worth honouring on a part of this size: the program array is eight kilobytes of file, and it is usually accompanied by a decode of the configuration word and a table of the port assignments. Delivering those as separate items rather than as one long file keeps the derived information visible.

Where a smaller part shows the same port-reading technique with far fewer pins to work with, PIC12C509A binary covers the constraint. Where a forty-pin sibling in the same generation has a parallel bus instead of a display, PIC16F747 code reads that interface. And where the ports drive an analogue front end rather than a display, PIC16C717 program follows the constants.

The short version

On a forty-pin part the port writes are the schematic. Look for a multiplex loop, a keypad scan and a bit-banged clock, extract the counts and the tables, and write the result as a pin table with the inferred rows marked as inferred.

PostHeaderIcon ATmega261: Reading Two Images Against Each Other

One image describes a program. Two images of the same program at different points in its life describe a development process, and that is frequently the more useful artefact.

The technique is simple: align the two files, mark every differing byte, and classify the differences. What emerges is a map of where the design changes — and, just as informative, where it does not.

Why two images beat one

A single image requires the analyst to decide, from nothing, which parts are logic and which are constants. Two images resolve that by observation: regions that never change across versions are likely to be structural, and regions that change with every build are likely to be parameters or version strings.

Three specific questions become answerable:

What changed in the last firmware update? The diff is the changelog, and it is more honest than the release notes.

Which values are per-unit? If two units of the same firmware differ in a small region, that region holds calibration.

Where does the version identifier live? It changes between releases and not between units, which distinguishes it from calibration.

Reading the diff

Aligned byte-wise, a diff of two builds usually shows four kinds of region:

Pattern Interpretation
Large identical block Unchanged code, the bulk of the program
Scattered single-byte changes Constants or immediate values adjusted
Contiguous changed block A function rewritten or added
Short changed string A version or identifier

The third kind is the interesting one, and its length is a measure of the change. A twenty-byte change is a constant; a four-hundred-byte change is a function.

The alignment problem

Two builds of the same source with small changes produce images whose addresses shift, because code size changes propagate. A naive byte-wise diff then reports the entire tail as different, which is useless.

Two practical approaches:

Anchor on the vector table and on identifiable strings. These are stable across builds, and they let you align regions rather than bytes.

Diff at instruction level rather than byte level. Decoding both images and comparing instruction sequences tolerates insertion better, because a decoder resynchronises at the next valid instruction.

Neither is perfect. A function inserted early in the image shifts everything after it, and only instruction-level comparison with explicit resynchronisation handles that reliably.

What per-unit comparison reveals

Two devices running identical firmware differ only where the firmware reads data that is unique to the unit. That is typically EEPROM rather than flash, but not always: some builds store a serial in the flash at production time, written after the main image.

A flash region that differs between two units of the same version is therefore a strong indication of production programming, and the region’s size and structure usually identify what it holds — a few bytes for a serial, a table for calibration.

Limits worth stating

Differential reading assumes the two images are the same product. Two different products built on the same part will differ everywhere and yield nothing.

It also assumes both images were read correctly. A single marginal byte in one file produces a false difference, and a false difference in a structural region invites a wrong conclusion. Verifying each page twice before diffing is not optional.

Finally, the technique describes change, not intent. It says what was edited, not why — and the why is what a reconstruction has to supply.

Practical sequence

  1. Read both devices fully, each page verified twice.
  2. Record signature, fuses, and lock byte for each; discard the comparison if they differ.
  3. Align on the vector table and on strings.
  4. Classify differing regions.
  5. Report both the diff and the unchanged bulk, because the second is what the design kept.

Related notes

FAQ

Does a diff require identical part numbers? Yes, ideally, and at minimum the same signature and fuse settings.

Can a diff work across firmware versions far apart? It degrades as the code diverges. Beyond a point the images share only their framework.

What does an unchanged region prove? That the design kept it. Not that it is correct, only that it was not revised.

Why verify before diffing? Because a single misread byte creates a false difference, and false differences in structural regions mislead.

Conclusion

Two images turn an inference into an observation. Structure, constants and version identifiers separate themselves, and the unchanged bulk describes what the design held constant. The technique’s limits are real — same product, clean reads, aligned on anchors — and within them it is the fastest route to understanding a program’s history.

PostHeaderIcon Recover Chip PIC16C642 Binary: Byte Order, Packing and the Off-By-One That Survives Verification

An Intel HEX file carries its own structure: addresses, lengths, checksums. A binary file carries none of that. It is a sequence of bytes whose meaning is entirely positional, and every property that makes it convenient — it is small, it loads without parsing, it can be compared byte for byte — is a property that removes a check.

On a part like the PIC16C642, an eighteen-pin OTP device with a thousand words of program memory, the whole image is two kilobytes or less. That is small enough to be handled casually, and casual handling is how the errors get in.

What the file can and cannot say

Information In a HEX file In a binary file
starting address per record assumed zero
gaps in the map recorded as absent indistinguishable from data
length of the image explicit implied by file size
integrity per-record checksum none
word width implied by convention implied by nothing

The second row is the one that causes the most damage. If the program array has an erased region in the middle — which happens when a design was built in two stages and the linker left a gap — a binary file contains that region as two hundred bytes of 0xFF, and there is no way to tell from the file whether those bytes are a gap or actual data.

The fifth row is the one that causes silent corruption. Fourteen-bit words stored as two bytes have two common conventions, and a binary file states neither.

The two packing conventions

Convention Bytes per word Word 0 as bytes Result of using the wrong one
low byte first, high byte padded 2 instruction encoded with the top two bits unused every pair of words fused
packed, no padding 1.75 on average bits run continuously every word shifted by two bits

The first is what most tools for this family produce. The second appears when an image has been through a converter written for a different architecture, and it is rare but not unknown.

The failure of the first when a file is actually packed, or the reverse, is that the file length changes. A thousand words is two thousand bytes under the padded convention and seventeen hundred and fifty under the packed one. A quick length check against the expected value catches it before anything else does.

The off-by-one that survives verification

The most dangerous class of error is the one that shifts the entire image by a small amount: one byte, two bytes, or one word.

A one-byte shift produces a file where word zero is composed of the second byte of the first word and the first byte of the second. It decodes to a plausible instruction — on a fourteen-bit core almost any value decodes to something — and the image runs for a few instructions before doing something wrong.

Why it survives verification: a programmer’s verify compares the file against its own buffer, and both contain the same shifted data. The comparison passes. The device does not work, and the file is blamed.

The test that catches it does not involve the programmer. It involves the first word and the last word. Word zero must decode to a jump or a load, because that is what a reset vector is. The last word of the code region must be followed by blank, because that is how a linker ends a program. A shift damages both.

Where a shift comes from

Three sources, all mundane.

A tool that strips a header or a comment block from the start of a file, counting bytes rather than records, removes data as well. A tool that prepends a length or a device identifier does the same in the other direction. And a manual edit — inserting a byte in a text editor or a hex editor without noticing the mode — leaves the file one byte long.

The third is more common than it should be, because binary files are often “fixed” by hand when a single value needs changing.

Reading the image for structure rather than content

Once the packing and the offset are established, the reading of a thousand words is straightforward. Three features give the layout.

The interrupt vector, if the design uses interrupts, sits a few words in and contains a jump to a handler. A design without interrupts has a return instruction there, which is a useful negative signal.

The main loop is the block with the most backward jumps. Counting how many branches target each address and sorting by count gives the loop structure in a few seconds, and on hand-written code the main loop is always at the top of that list.

The tables are runs of similar instructions with different literals. On this core they are usually returns carrying values, and they are found by looking for the highest density of identical opcodes.

Feature Signature What it establishes
reset vector word 0 is a jump file offset and packing are correct
interrupt vector jump at the vector address, or a return whether interrupts are used
main loop most-targeted address the top-level structure
tables runs of identical opcodes with varying literals constant and dispatch content
blank tail long run of 0x3FF end of code and start of padding

The first and last rows together are the whole verification. If word zero is a jump and the blank tail starts at a plausible address, the file is almost certainly aligned.

Delivering both formats

The practical answer is to deliver the binary and the HEX, because each catches the other’s failure. The HEX records the addresses and the checksums; the binary is what a production programmer often wants. A customer who has both can tell a shifted file from a correct one by comparing the two.

Alongside them, a short note stating the packing convention, the address range covered, and the addresses that are blank rather than absent.

Where an eight-pin part shows the same packing problem on a much smaller image, PIC12C509A binary covers the interface side. Where the same family’s larger member has the page structure that this part lacks, PIC12CE519 flash sets out the address model. And where the file format question is about records rather than offsets, PIC12F609 heximal covers the HEX side.

The short version

A binary file knows nothing except position. Check the length against the expected word count, confirm that word zero is a jump and that the blank tail begins where it should, and deliver both formats so that one can audit the other.

PostHeaderIcon ATmega861PA: Why the Addresses Move When the Code Grows

An image is laid out in order, and there is no padding between sections by default. Insert an instruction anywhere before the end and everything after it moves by the size of the insertion. Nothing is wrong; the addresses are simply different.

That has a consequence for anyone comparing two builds or matching a disassembly against documentation: an address is not an identity. A function at a given address in one build may be something else entirely in the next.

Where the layout comes from

The linker places sections in a fixed order and, within each, in the order the objects were supplied. Four sections are typical:

Section Content Moves when
Vectors Jump table Rarely; fixed size
Text Code Any code change before it
Data initialisers Initial values for SRAM Code size changes
Constants Tables, strings Code size changes

The first row is the only stable one. Everything after the vector table is positioned relative to the code that precedes it, which is why an address found in one build rarely means anything in another.

What this does to a comparison

Two builds of the same product, one with a bug fix, produce images that differ from the point of the change onward. A byte-wise diff reports the whole tail as different, which is useless.

The way to compare meaningfully is to compare structure rather than addresses:

  • Align on the vector table, which does not move.
  • Align on string and constant blocks, which keep their content even when their address changes.
  • Compare instruction sequences rather than byte offsets.

With those three anchors, a diff of two builds becomes a list of actual changes rather than a list of shifted bytes.

What it means for a reconstruction

A reconstruction is a description tied to addresses: “the routine at this address does that”. If the description is ever applied to a different build, the addresses are wrong and the description misleads.

The fix is to record structure alongside address. A function described as “the third handler in the table, calling the shift routine twice” survives a rebuild; one described as “the code at address 0x04A2” does not.

That is not pedantry. Products get reflashed, revisions get rebuilt, and a report that only works for one binary has a short useful life.

The array limit and what happens near it

As an image approaches the size of its array, two things occur:

Constants get pushed. Tables that were inline move to the end, because there is no room between functions. The image’s tail becomes a data region rather than code, which changes how it should be read.

Jump distances grow. Branches that were short become long, which changes instruction sizes and therefore shifts everything again. On a part where the boundary between relative and absolute jumps is near, a small addition can convert a whole region.

Both effects are visible if the image is examined near its limit, and both are invisible if it is not. Knowing which case applies sets the expectation for how much of the tail will disassemble as code.

Reading an image near its limit

  1. Read the array, verified page by page.
  2. Decode the vector table; confirm the reset target.
  3. Read forward from the target until instructions stop making sense.
  4. From that point, treat content as data and check for structure: fixed stride, printable text.
  5. Record the boundary and describe the function in structural terms.

Step 4 is the one that prevents the classic error of disassembling a constant table as code. Tables have regular stride and no valid opcodes; code has irregular structure and valid opcodes. The transition is usually obvious once it is being looked for.

Related notes

FAQ

Do addresses stay the same across builds? Only the vector table. Everything after it moves with code size.

Can a diff be done without alignment? It can be run. The output will be mostly false differences.

Why do constants end up at the end? Because the linker places them after code when there is no room inline.

Is a disassembly of the data region harmful? It produces plausible nonsense that can be mistaken for obfuscation.

Conclusion

Addresses are a property of a build, not of a program. Comparing builds needs structural anchors, and describing a program needs structural language, because the numbers change the moment anyone edits the source. Recording structure is what makes an analysis survive the next release.

PostHeaderIcon ATmega861PV: Finding the Version String and What It Tells You

Before anything in an image is understood, it is worth finding what the firmware says about itself. Product code, version, build date, protocol revision — these appear as literal strings or as packed constants, and they are usually the first thing with an obvious structure.

The value is not the string itself. It is what the string dates and what it distinguishes, which is often the difference between examining the right firmware and the wrong one.

Where identifiers live

Four common forms, in order of ease:

ASCII strings in flash. Sequences of printable characters, usually null-terminated. Trivially visible in a hex view and the fastest route into an unknown image.

Packed characters in constants. Two characters per word, or a version encoded as three bytes. Less obvious, still recognisable because the byte values cluster in the printable range.

Numeric constants. A version as a 16-bit number, incremented per release. Finding it requires noticing a value that appears in comparisons.

Stored in EEPROM. Written at production, unique per unit. This is where serial numbers live.

Form Effort to find What it identifies
ASCII string seconds Product and firmware version
Packed constant minutes Version, sometimes a build stamp
Numeric constant longer Release number
EEPROM field one extra read The individual unit

Why the string matters more than it seems

Three uses.

Confirming you have the right device. A customer sends two units and one has different firmware. The string distinguishes them before any analysis, which prevents comparing two different products.

Dating the firmware. A version paired with a release history dates the build, and that dates the hardware revision it was written for. On a product with several hardware revisions, this is the fastest way to establish what you are looking at.

Finding the reference point. A string is a known-content region, which makes it an anchor for alignment and for locating the tables around it. Constants are usually stored near the code that uses them, so a string often sits adjacent to the configuration block.

What a string reveals about structure

The bytes around an identifier are informative. A version string followed by a table of similar-length strings is a message catalogue — which means the product reports errors, and the errors describe what can go wrong.

That is frequently the single most valuable artefact in the image. An error catalogue names the failure modes the designer anticipated, and each message points at the code that generates it.

Reading order adapted for identification

  1. Signature, fuses, lock byte.
  2. Array read, verified.
  3. Scan for printable sequences; record every one with its address.
  4. Cluster them: message catalogue, protocol strings, identifiers.
  5. Read the code around the identifier to find the version comparison.

Step 5 is where the string becomes a tool. Code that compares a version against a constant is a protocol negotiation, and finding it explains how the device interacts with whatever it talks to.

The picoPower and low-voltage angle

Neither suffix affects the strings. What they affect is the reliability of the read, and a misread image produces strings with corrupted characters — which is actually useful, because an apparently garbled string is an immediate signal that the read is suspect.

That makes string extraction a crude integrity check as well as an identification step. On a device read at a marginal supply, the strings are the first place damage shows.

Related notes

FAQ

What if the image has no strings? Small builds often have none. Then identification comes from the constants and from behaviour.

Can a version string be faked? It can be edited. A string that disagrees with the code’s behaviour is a signal in itself.

Why scan before disassembling? Because strings give an anchor and a summary for a fraction of the effort.

Do garbled strings mean the firmware is corrupt? Usually they mean the read was marginal, not that the device is damaged.

Conclusion

An identifier is the cheapest piece of information in an image and one of the most useful. It confirms the device, dates the firmware, provides an anchor for alignment, and often sits next to the message catalogue that describes the product’s failure modes. Extracting it takes minutes and orients everything that follows.