STM32F723: What Two USB Controllers Tell You About a Product
Products built on this member usually have exactly one cable: a USB one. It powers the device, it carries the data, and in many designs it is the only external interface the product has. That makes the USB stack the most customer-visible part of the firmware and, from a recovery point of view, the most legible.
Two controllers are present. One reaches high speed through an external physical-layer device; the other has its transceiver on chip. A design that uses both is doing something specific — a fast data pipe to a host and a separate port for configuration or debugging — and that arrangement is visible in the image before any code is disassembled.
The enumeration tables
A USB device describes itself to the host through a hierarchy of descriptors. The host asks for them immediately after attachment, so they are the first structured data the firmware produces, and they are stored in the image in essentially the form they are sent.
| descriptor | what it declares | what it reveals about the product |
|---|---|---|
| device | vendor and product identifiers, class, maximum packet size | who the product claims to be, and whether it uses a custom or a standard class |
| configuration | power draw, number of interfaces, attributes | whether the device is bus-powered and how much it asks for |
| interface | class, subclass, protocol, endpoint count | what the product does: storage, serial, audio, vendor-specific |
| endpoint | address, direction, transfer type, interval | how the data flows and at what rate |
| string | human-readable text in UTF-16 | manufacturer, product name, serial-number scheme |
The identifiers in the first row are the fastest way to establish provenance. A vendor identifier that belongs to a known semiconductor vendor indicates a development or evaluation build; one that belongs to the customer indicates production firmware; a generic identifier shared across many products indicates a copied reference design. None of these is conclusive on its own, and together they narrow the question sharply.
Strings are the cheapest evidence
String descriptors are stored as UTF-16, which means they appear in a hex dump as alternating characters and zero bytes. They are legible without any disassembly at all, and they are frequently the most informative thing in the image.
| what a string descriptor contains | what it means for the job |
|---|---|
| a product name with a version number | the firmware revision, which the customer can verify against their records |
| a serial-number format with a prefix | the scheme used to derive serials, often from the device identifier |
| an interface name referring to a protocol | which protocol stack the product implements |
| a manufacturer name that is not the customer’s | the design was outsourced or licensed |
The second row is worth following up. A serial number derived from the device’s unique identifier is a design decision with a consequence: a restored image on a different die will present a different serial, and any host-side licensing or pairing that depends on that serial will break. That is the same class of problem as an identifier-derived encryption key, described in STM32F756 code recovery, and it is easier to spot here because the string is readable.
The factory path
Every device of this family contains a bootloader in a read-only region that cannot be erased. It is reachable by holding the boot configuration pins at reset, and it speaks over several interfaces, one of which is USB.
This is the path most often proposed as a recovery method, and it deserves a precise description of what it can and cannot do.
| command class | what it does | limitation |
|---|---|---|
| identify | reports the supported commands and the flash geometry | works regardless of protection |
| erase | mass-erases the array | works regardless of protection, and destroys the contents |
| write | programs data at an address | blocked in protected regions |
| read | returns memory contents | refused when read protection is active |
| jump | transfers control to the application | used to leave the bootloader without a reset |
The fourth row is the decisive one, and it is the row that customers most often assume works. Reading memory through the factory bootloader is a privileged operation that the protection mechanism specifically disables; a device with read protection set will accept the erase command and refuse the read command, and the two responses arrive through the same channel from the same firmware. A bench that reports “connected to the bootloader, therefore readable” has established the first half and assumed the second.
The converse is equally worth stating. A device with protection unset can often be read entirely through this path, without opening anything, in a few minutes. That makes the protection state the single most valuable piece of information to obtain before quoting work, and it is obtainable by attempting one read of a single word.
Class requests as a behavioural summary
Beyond enumeration, the requests a device supports describe what it is for.
| request pattern | the design it implies |
|---|---|
| bulk endpoints with large maximum packet sizes | a data pipe: streaming, storage or a custom protocol |
| interrupt endpoints with short intervals | periodic status reporting, often a sensor or a control panel |
| isochronous endpoints | audio or video, with timing guarantees |
| a vendor-specific class with a command header | a proprietary protocol, and the header is the specification |
| standard device firmware update class | field-updatable, and the update image format is defined by the class |
The fourth row is where a recovery delivers the most value per hour. A vendor-specific protocol implemented over bulk transfers has a request header, a length field and a set of opcodes, and those are all in the image in a form that can be reconstructed into a usable specification. Customers frequently want that specification more than they want the code.
Reading a device whose only port is USB
If the protection is unset, the factory path is the simplest route and requires no disassembly. If it is set, the USB port is no help at all, and the work moves to the debug port — which may itself be disabled — and from there to physical access.
A useful intermediate check is the boot configuration at reset. A design that has tied the boot pins to fixed levels has removed the factory path from the product entirely, which is common in consumer hardware and rare in industrial hardware. Establishing that early prevents a plan built on a route that does not exist on this board.
The identification discipline that precedes all of this, for a device whose designation does not resolve, is in STM32F747 binary recovery. The same factory bootloader described from the command-set angle rather than the USB angle is in STM32F745 heximal extraction, and the member of the family without a network or display path, where USB is often the only interface at all, is in STM32F722 code recovery.
FAQ
Can USB alone recover a protected device? No. The factory bootloader refuses read commands when protection is active, and the application’s own USB stack is under the same restriction.
Are the descriptors always stored verbatim? Usually, and where they are generated at runtime the generator is short and recognisable by its table of lengths and types.
Does a serial number derived from the device identifier matter? It does if anything external depends on it — licensing, pairing or a host-side database. Restoring the image onto another die changes it.
Conclusion
On a product with one port, the port’s description of itself is the best documentation available. Descriptors give identity and purpose, strings give revision and provenance, class requests give behaviour, and the factory bootloader gives either a fast recovery or a fast refusal depending on a single option byte. Reading that byte first is the difference between a plan and a guess.
STM32F722: The Family Member Defined by What Is Missing
Marketing material describes microcontrollers by what they include. Recovery work is the other way round: the fastest way to know what you are looking at is to establish what cannot be there. A part that has no display controller cannot be running a display initialisation sequence, and an image that contains one came from somewhere else.
On this member of the family the list of absences is unusually informative, because the die is a deliberate reduction of a larger one. Same core, same instruction set, same development tools, and several blocks removed to make a cheaper part.
What was removed
The reductions are consistent and they cluster around connectivity and graphics.
| block | present on the larger members | present here |
|---|---|---|
| Ethernet MAC | yes | no |
| display controller | yes on most | no |
| graphics accelerator | yes on most | no |
| camera interface | yes | no |
| flash width | 256-bit lines | 128-bit lines |
| instruction and data cache | 4 KB or 16 KB each | 8 KB each |
| flash capacity | 1 MB or 2 MB | 512 KB |
| general-purpose SRAM | 240 KB or 368 KB | 176 KB |
The last four rows are the ones that matter technically, because they change the shape of the image rather than just its contents. The first four change what a recovery is looking for.
| absence | what it removes from the job |
|---|---|
| no Ethernet | no network stack, no address configuration stored in flash, no packet buffers to reconstruct |
| no display controller | no timing configuration, no layer descriptors, no frame buffer mapping |
| no graphics accelerator | no asset tables, no colour lookup structures, no blending configuration |
| no camera interface | no image pipeline, and no large double-buffered capture regions |
That is a substantial reduction. On the larger members of this family, display and network configuration routinely account for a third of the structured content in an image, and reconstructing it is a large part of the analytical work. Here it is not present, and an image that contains it is not an image of this device.
The narrower flash path
The flash array on this part is read in 128-bit lines rather than the 256-bit lines of the larger members. That single difference has visible consequences.
| effect | how it appears |
|---|---|
| code layout | functions align to a smaller boundary, so the image looks denser |
| prefetch behaviour | the accelerator in front of the array issues more transactions for the same span of code |
| a copied routine | code relocated into tightly coupled memory shows a 128-bit alignment in the copy loop |
| an image from a larger member | 256-bit alignment in the same places, which is visible and inconsistent |
The last row is a practical test rather than a theoretical one. Alignment is not something a compiler randomises; it follows the bus width and the linker’s section placement. An image whose function boundaries consistently align to thirty-two bytes came from a part with a 256-bit path, and finding that on a board labelled as this member means either the label or the image is wrong.
Protection on a part with fewer places to hide
Read-out protection works the same way as on the rest of the family, with the two levels and the same irreversible character at level two. What differs is the number of places data can be concealed.
A part with a display pipeline has external memory, and external memory is a place to store things that are not in the internal array. A part without one usually has no external memory at all, which means everything the product knows is either in the internal flash, in RAM while running, or in a peripheral register. That is a shorter list, and it makes a complete accounting feasible in a way that it is not on a board with three memory devices.
| storage location | typical contents | how it is read |
|---|---|---|
| internal flash | code, constants, configuration tables | direct read |
| one-time programmable area | serial numbers, calibration, sometimes a key | direct read, write-once by construction |
| RAM | runtime state, session keys, buffers | read with the device halted and the core’s stores settled |
| option bytes | protection state, watchdog behaviour, boot configuration | decoded from a read of the option block |
The second row is worth checking on every job of this kind, because a one-time area is where a design puts a value it does not want changed, and a recovery that skips it delivers an image without the calibration data that makes the product work. The size is small — around a kilobyte — and reading it is one transaction.
What the smaller cache does to a live read
Eight kilobytes of data cache is small enough that a running program evicts constantly, which is fortunate for extraction: the window in which a dirty line sits unwritten is shorter than on the larger members, so a probe read is more likely to be current. It is not a guarantee, and the procedure is the same — halt, settle the stores, read — but the failure rate is lower.
The cache behaviour itself is described in STM32F746 software recovery, which deals with the larger cache and the same underlying problem. A worked case on this exact part number, where the firmware was encrypted and the recovery had to establish that the plaintext was already in flash, is at STM32F722RET6 firmware restoration — the two pages address different questions about the same die and are worth reading together.
When the absences do not match
The most useful outcome of this line of reasoning is a contradiction found early. Three checks, each taking a minute.
First, look for a display initialisation sequence: a block of writes to consecutive registers with timing values that resolve to a plausible horizontal and vertical total. If it is there, the part is not what the label says, or the image is not from this board.
Second, look for a network stack: a byte-order-sensitive header parser, a set of buffers with a fixed maximum size, and an address structure in flash. If it is there, the same conclusion follows.
Third, check the flash size register and compare it with the capacity the image implies. An image that fills more than half a megabyte cannot have come from a device with a half-megabyte array.
| finding | conclusion |
|---|---|
| no display, no network, 512 KB | consistent with this member |
| display configuration present | the image came from a graphics member, or from a development build |
| network stack present | the image came from a member with Ethernet |
| image larger than the array | the file includes padding, or the read included a region that is not flash |
What the deliverable looks like
The report should state the identification and the evidence for it, including the absences checked. It should give the flash image with its length and the erased tail distinguished from programmed content. It should give the one-time area and the option bytes as separate small files, because they are small, they are independently useful, and bundling them into a single blob makes them easy to lose.
The identification discipline itself, for the case where the name on the order does not resolve at all, is in STM32F747 binary recovery. The boot-from-ROM path that remains available when the flash cannot be read normally is in STM32F745 heximal extraction, and the USB side of a device in this class, which is often the only external interface it has, is in STM32F723 binary recovery.
FAQ
Does the missing display controller mean the product has no display? Not necessarily. A display can be driven by an external controller over a serial or parallel interface, in which case the interface configuration is in the image instead of a display pipeline.
Is the smaller cache a disadvantage for extraction? It is an advantage, because dirty lines are evicted sooner. The procedure is unchanged.
Can an image from a larger member run here? It will not fit, and its alignment gives it away before anyone tries.
Conclusion
Absence is evidence. On this member the removed blocks define both the identity of the device and the scope of the work: no display pipeline to reconstruct, no network stack to map, a shorter list of places data can hide, and a flash path whose alignment betrays an image from elsewhere. Establishing what cannot be present is faster than confirming what can.
ATmega324V: What the Low-Voltage Grade Costs at the Bench
A V suffix means the part is specified to operate at a lower supply voltage than its standard sibling. In a battery-powered product that is the whole point: the same core, running down to a lower rail without losing its clock.
At the bench the suffix reads differently. A wider supply range invites the assumption that everything inside the chip works equally well at the bottom of that range, and that assumption is where read attempts go wrong. Three separate specifications govern a read, and they do not all extend downward.
Three minimums, not one
| Parameter | What it governs | Behaviour at the low end |
|---|---|---|
| Core supply minimum | Whether the CPU can execute | Extends downward on a V part |
| Programming supply minimum | Whether flash commands are accepted | Narrower than the core minimum |
| Oscillator accuracy window | Whether the interface timing is valid | Degrades before the supply limit is reached |
The middle row is the one that causes trouble. The programming interface is specified over a range that is typically narrower than the core’s. A part that happily runs its application at the bottom of its rail may still refuse, or silently mangle, a page read requested at that voltage.
The failure is quiet. The interface does not return an error for a marginal read; it returns bytes. Which bytes depends on which sense amplifiers resolved and which did not, and the result is a dump that looks like data.
The oscillator drifts before the supply fails
The internal RC oscillator is the default clock. Its accuracy is specified over a voltage and temperature window, and near the edges of that window it drifts — typically a few percent, which is enough to matter.
ISP is synchronous: the programmer drives the clock, and the target samples on edges derived from its own clock. If the target’s clock is 5 percent slow, the effective sampling margin shrinks by 5 percent. At a high SCK rate that margin was already thin, so the read starts failing intermittently — one page in twenty, then one in five. Intermittent is the worst kind of failure because the first few pages succeed and look convincing.
Lowering SCK is the direct remedy, and it costs only time. A read at a quarter of the nominal rate still takes seconds for a device of this size.
What the erase threshold does
Chip erase is a high-energy operation relative to a read. It needs the charge pump to reach its target, and the pump’s capability depends on the supply. At the bottom of the specified range an erase may complete but leave cells only partially erased, or the operation may time out.
A partially erased page reads as a mixture of FF and residual values. Nothing in the interface reports it. The way to detect it is to erase, read, and check that the whole array is FF before proceeding — a step that is easy to skip and expensive to skip.
Doing this check also validates the whole setup: supply, socket, adapter, clock. If the array reads FF everywhere after an erase, everything upstream is working.
Why a V part is not automatically easier
There is an intuition that a part rated for a lower voltage is somehow more robust or more accessible. Neither is true. The V grade is a specification about operating, not about reading, and the two are different modes with different limits.
Where the V grade does help: a device that runs from a 3.3-volt rail can be powered at its design voltage on the bench, which avoids the level-shifting and margin questions that come with a 5-volt part being run at 5 volts through a long cable. Matching the part’s own design rail is always the cleanest option, and a V part makes that easier because its design rail is lower.
Where it does not help: everything about the programming interface, which has its own specification and its own minimum.
A read procedure that survives the margins
The sequence below is deliberately conservative. Every step is cheap, and each one eliminates a class of error before it can contaminate the result.
- Power the part at its design rail, not at the bottom of its range.
- Read the signature. It is short, it exercises the interface, and a wrong signature means the connection is wrong before anything else is attempted.
- Read the fuse and lock bytes. Confirm interface enable fuses and lock state.
- Read one page twice at a modest clock. Byte-for-byte identical means the margin is adequate.
- Raise the clock only if the full read would otherwise be impractical, and re-verify the first page after raising it.
- Read the full array. Re-read the first and last pages at the end; if they still match, the read is trustworthy end to end.
Step 6 catches the case where the part warmed up, or the supply sagged, midway through a long read. It is the cheapest possible integrity check on the whole operation.
Related notes
- ATmega324PA and brown-out reads — the detector that guards against exactly this.
- ATmega168PV and the calibration byte — how the trim value is stored and why it drifts.
- ATmega164 and the boot section — the other protection field on the same die family.
FAQ
Does a V part work at 5 volts? Usually yes, within its upper limit. Lowering the supply is the direction to be careful about, not raising it.
Can a read at too low a voltage damage the part? A read does not damage cells, but an incomplete erase at low voltage can leave a page in an indeterminate state that a later operation must clean up.
Why does the dump differ between two runs? Because the margin is inadequate. Voltage, clock rate, or socket contact — in that order of likelihood.
Is the V grade relevant to lock bits? No. The lock bit mechanism is identical across grades. Only the electrical envelope differs.
Conclusion
The V suffix is a statement about operating voltage, and a read is not the same mode as operating. Programming has a narrower envelope, and the oscillator drifts before either limit is reached. Powering at the design rail, reading twice, and verifying the ends of the array turns a marginal setup into a reliable one without any change to the protection story.
STM32F769: Seven Timing Numbers That Describe a Memory Chip You Cannot See
A recovery request usually names one device. On a board built around this part, the firmware may be spread across three: the microcontroller, a serial flash holding the application image, and a parallel memory holding everything that did not fit — frame buffers, sample buffers, and sometimes the code itself.
The third device is the one that gets overlooked, because it is not where anyone expects firmware to live. It is also the one that can be described most precisely from inside the image, because the controller that drives it is configured by software, and that configuration is a compressed description of the memory part.
What the controller has to be told
Before the external memory can be used, the program writes a geometry word and two timing words. The geometry says how the part is organised; the timing says how slow it is.
| field | what it declares | typical values |
|---|---|---|
| number of column address bits | columns per row | eight, nine, ten or eleven |
| number of row address bits | rows | eleven, twelve or thirteen |
| memory width | data bus width | eight, sixteen or thirty-two bits |
| internal bank count | whether the part has two or four banks | two or four |
| column address strobe latency | clock cycles from command to data | two or three |
| read burst and write burst mode | whether bursts are single or sequential | usually sequential |
Those six fields together describe the addressable capacity, and the arithmetic is direct: rows times columns times width times internal banks. A part with thirteen row bits, ten column bits, a sixteen-bit bus and four internal banks holds sixty-four million bits of storage organised as four million sixteen-bit words. That number either matches the device on the board or it does not, and if it does not, the configuration came from a different hardware revision.
The timing words
The timing registers hold the values that describe how the memory responds, expressed in controller clock cycles.
| parameter | what it constrains | what a large value suggests |
|---|---|---|
| row address strobe to column address strobe delay | how quickly a second access in the same row can begin | a slower part, or a slower clock |
| row precharge delay | time to close a row | likewise |
| row active time | minimum time a row stays open | older or larger geometry |
| row cycle time | row-to-row access | the dominant limit for scattered access |
| exit self-refresh to active | recovery from low power | a part with a long wake-up |
| mode register set to active | initialisation | rarely discriminating |
| write recovery time | time after a write before precharge | distinguishes faster parts from slower ones |
Individual values are not a fingerprint. The set of them is: a design configures the controller for the exact part it was built with, and the ratios between the parameters follow the part’s speed grade. A controller configured for a fast part on a board that now carries a slower one will work until the temperature rises, and the failure will present as random corruption that the customer attributes to the firmware.
Why code ends up out there
Executing from external memory is possible on this family, and it is a rational choice when the application is large and the internal array is not. What matters for a recovery is that the decision leaves a signature.
| signature in the internal image | what it means |
|---|---|
| controller initialisation before any large allocation | the design depends on external memory from early in startup |
| a copy loop with a source in the serial-flash region and a destination in the external memory region | the application is staged through two external devices |
| a jump to an address in the external memory range | execution continues outside the chip |
| a linker script boundary at the end of the internal array with a large vector table offset | the vector table was relocated |
The third row is the decisive one. Once execution continues outside the die, the internal dump is a boot stage and the external device holds the code that does the work — which is the same conclusion as on the value-line member, reached from a different direction, and covered in STM32F750 flash recovery.
Reading the external part
Three approaches, ordered by how much they depend on the firmware working.
| approach | how | reliability |
|---|---|---|
| through the controller, with the device running | read the memory-mapped range with the processor halted | high, if the controller is initialised and protection permits |
| through the controller, with a debugger writing the initialisation | reproduce the configuration, then read | high, but the configuration has to be correct first |
| directly from the memory device | connect to the part and read it as it is | highest, and independent of the firmware entirely |
The second is the one that most benches use and the one that produces the most interesting failures, because reproducing the configuration requires knowing the timing values exactly, and a controller configured with slightly wrong timings reads plausible data that is occasionally wrong. The third is slower and requires access to the board, and it removes the whole class of doubt.
When the readings disagree with the marking
A board whose controller is configured for a different part than the one fitted is not rare. It happens after a component substitution during a shortage, after a board revision that changed supplier, and after a repair in which the memory device was replaced with the nearest available equivalent.
The symptom pattern is distinctive. Reads through the controller succeed and every so often return a value that fails a checksum. The failure rate rises with temperature and with access speed. A direct read of the device returns correct data every time. That combination — intermittent through the controller, perfect directly — is the signature of a timing mismatch rather than a bad memory.
| symptom | likely cause |
|---|---|
| intermittent single-word errors that scale with temperature | timing values too aggressive for the fitted part |
| reads that work at low clock and fail at full speed | same |
| a geometry that implies a larger part than is fitted | configuration left from another board revision |
| data correct through the controller and correct directly | no fault; the earlier errors were elsewhere |
What the report should state
State the geometry as decoded, with the capacity the arithmetic produces. State the timing set and whether it is consistent with the part identification. State whether execution continues into external memory, and if so at what address. And state whether the external device was read directly or through the controller, because the two answers carry different confidence.
The banked internal array that sits alongside this external memory, and the boundary that falls in the middle of it, is covered in STM32F767 firmware recovery. The cache behaviour that decides whether a read of a buffer is current is in STM32F746 software recovery, and the case where the external device holds ciphertext rather than plaintext is in STM32H725 heximal recovery.
FAQ
Can the external memory be read without desoldering? Usually, either through the controller with the device running or by halting the core and taking over the controller. Both depend on the firmware not having protected the region.
Does a flat internal dump include the external memory? No. It is a separate device with a separate address range and a separate read.
Is the timing configuration reliable enough to identify the part? It narrows the candidates to a speed grade and a geometry. The package marking on the device itself settles it.
Conclusion
The external memory is the part of the board that a recovery plan most often forgets, and the controller configuration is the part of the image that describes it best. Geometry gives capacity, timing gives speed grade, and the presence of a jump into that range tells you the internal dump is only the beginning. Reading the registers costs nothing and prevents the most common incomplete delivery on this class of board.
STM32F767: Two Megabytes, Two Banks, and a Boundary at Half Way
Two megabytes of flash on a microcontroller sounds like a simple quantity, and the tools treat it as one: a length, an offset, a single block of output. The silicon does not work that way. The array is two banks of one megabyte each, with a boundary in the middle that is not visible in a hex dump and that governs erase granularity, write protection and the possibility of updating half the device while the other half runs.
None of this stops a read from succeeding. It stops the conclusions drawn from the read from being right.
Where the boundary is
The first bank occupies the lower megabyte of the flash address range and the second bank occupies the upper megabyte. The boundary falls at the address one megabyte above the flash base, which in a flat image is offset one million and forty-eight thousand five hundred and seventy-six.
| range | contents | what it governs |
|---|---|---|
| lower bank, first megabyte | normally the program, its vector table and its constants | the reset vector and the entry point |
| upper bank, second megabyte | a second image, application data, or nothing | the update path and the fallback |
The row that matters is the third column. A device that uses the upper bank for a stored update has a program in the lower bank and a payload in the upper one, and a flat dump of two megabytes contains both with nothing marking the join. A reader who assumes the second megabyte is unused space will treat a valid second image as garbage, and a reader who assumes it is a continuation of the first will disassemble the join as a single program and find nonsense at the seam.
| observation in a flat dump | the wrong conclusion | the better reading |
|---|---|---|
| code ends before the boundary, upper bank erased | the device has one small program | consistent with a device whose second bank was never populated |
| upper bank contains a plausible vector table | the array holds two programs | an update is staged, or the device runs from the upper bank |
| a valid program in each bank with different sizes | the image is corrupted | two firmware versions are both present |
| upper bank starts with a header and a length field | the data is unreadable | a packaged update, not code |
Erase granularity depends on the mode
This is the detail that catches people who work from memory of another family. The sector size on this part is not a constant; it follows the bank configuration.
| configuration | sector size | sectors | effect on the image |
|---|---|---|---|
| single-bank mode | 256 KB | eight across the whole array | erase units are large, so small updates rewrite a lot |
| dual-bank mode | 128 KB | eight per bank | finer granularity, and the two banks can be treated separately |
The consequence for a recovery is about interpreting what is blank. In single-bank mode, an erased region is at least 256 KB, and a program that occupies less than that leaves a large uniform expanse of erased values. In dual-bank mode, the same program leaves 128 KB boundaries. Looking at the size of the erased runs is therefore a way to infer the mode the device was configured in, which in turn tells you whether the design intended to update one bank while running from the other.
That inference is worth making because it is cheap and it is independent of anything the customer says. A device whose firmware uses a bank-swap style update will show a clean 128 KB alignment; a device that never updates in place will show whatever alignment the linker chose.
Read while write
The reason the part has two banks is that one can be erased and programmed while code executes from the other. This is the hardware basis of every robust field-update design, and it is also the mechanism that produces the most confusing partial states.
An update proceeds in stages: the new image is received and programmed into the idle bank, verified, and then a marker is written that makes the next reset start from the new bank. Interruption at each stage leaves a different artefact.
| interruption point | what the device holds | what the next boot does |
|---|---|---|
| during programming of the idle bank | a partial image in the idle bank, the old image intact | runs the old image, because the marker was never written |
| after programming, before the marker | a complete new image that is not in use | runs the old image; the new one is orphaned |
| after the marker, before the old bank is erased | two complete images, one designated | runs the new image |
| during erase of the old bank | one complete image and one partially erased | runs the new image, with no fallback |
The second row is the one that generates support tickets. The device behaves exactly as it did before the update, the customer reports a failed update, and the actual state is that the update succeeded and was never activated. A recovery from such a device returns two programs, and the question is not which one is valid but which one the marker points at.
Write protection is set per bank
Protection of the array against writing is configured in option bytes, with a granularity that follows the regions rather than the whole device. That matters because a design can protect the lower bank — the one that must not be damaged in the field — and leave the upper bank writable for updates.
When such a device is recovered, the protection state is part of the answer. A customer who intends to restore the device needs to know which regions will refuse programming, and a customer who intends to modify the firmware needs to know that the modification will be rejected by hardware rather than by software. Reading the option bytes and decoding them into a list of write-protected ranges takes a minute and prevents an afternoon of confusion.
The read-out protection is a separate mechanism with its own two levels, and its interaction with the boot path is described in STM32F745 heximal extraction. The contrast with a family that goes further and lets the two banks exchange their base addresses at runtime is in STM32H743 flash recovery — there the mapping itself is configurable, which produces two correct but different answers to the same question.
Reading the array correctly
Three steps, in order. First, read the option bytes and decode the bank mode and the write protection. Second, read the array as a whole; the read itself is indifferent to banks. Third, analyse the result bank by bank, because the analysis is not indifferent.
The third step is where the join has to be handled explicitly. A vector table at the flash base tells you where the program starts. A second vector table at the boundary tells you that a second program exists. Neither tells you which one runs, and the answer to that question lives in the option bytes or in a marker written by the update logic, which is why the first step comes first.
What the report should say
The report should name the bank mode, the sector size that follows from it, the write protection ranges found, and the contents of each bank as a separate statement. Where two images are present it should say which is designated active and on what evidence, and where a staged update was interrupted it should say so plainly rather than presenting a partial image as a corruption.
The RAM map that sits below all of this, and misleads anyone who assumes it follows the usual pattern, is in STM32F765 heximal recovery. The external memory path that often holds the rest of a large application is in STM32F769 program recovery, and the case where the internal array is far too small to hold the product at all is in STM32F750 flash recovery.
FAQ
Does the part support swapping the two banks’ base addresses? Not in the sense that later families do. The banks have fixed addresses; what an update changes is which bank the boot process uses, recorded in configuration rather than by remapping the array.
Is a two-megabyte dump always two megabytes of program? No. It is two megabytes of array, which may contain one program, two programs, one program and a staged update, or a program and a great deal of erased space.
Can the array be read while the device runs? Yes, and the bank that is not executing can be erased at the same time, which is the whole point of the arrangement.
Conclusion
The quantity is two megabytes; the structure is two banks with a boundary, a mode-dependent sector size and separate protection. Reading the bytes is the easy part, and it is also the part that creates the false confidence. Naming the banks, the mode and the designated image is what turns a dump into a description of a device.
STM32F745: The Program in ROM That Decides How the Job Starts
Before any bench equipment is connected, a device of this family is already running a program that its owner did not write and cannot remove. It sits in a read-only region of the address space, it is entered by holding configuration pins at reset, and it implements a command set over a serial interface. It exists so that a blank device can be programmed at the factory, and it remains in every shipped product.
That program is the first thing worth talking to. Its responses classify the device in a way that no amount of documentation can, and its limits are precise enough to plan around.
What it is and how it is entered
The bootloader is selected at reset by sampling two configuration pins. One combination boots the application from flash, another enters the loader, and a third is reserved. Designs wire these pins to fixed levels or to a connector, and the choice is visible on the schematic in about thirty seconds.
| pin state | what the device does at reset |
|---|---|
| both low | boots the application from flash |
| one high | enters the factory bootloader, which then waits on a serial interface |
| the other high | reserved, behaviour not defined for application use |
| both high | enters the factory bootloader |
The loader negotiates a serial protocol: a synchronisation byte, an acknowledgement, then commands. It runs from an internal oscillator, so it works on a board with a dead crystal, a shorted peripheral or a program that never reaches its first instruction. That is the reason it is worth trying first on a device described as dead.
The command set
The set is small, and each command answers a different question.
| command | what it returns | what it establishes |
|---|---|---|
| initialise | the loader version and the list of supported commands | that the interface works and what else is available |
| identify | the device identification word and the flash size in kilobytes | which die is fitted and how large the array is |
| read | the contents of a memory range | the image, when permitted |
| write | acknowledgement, or an error | whether the target region is writable |
| erase | acknowledgement after a mass erase | that the array is now blank, irreversibly |
| read protection status | whether protection is set | the classification that decides the whole job |
| jump | none, the device restarts into the application | a clean exit without a power cycle |
The second row is useful even when nothing else works. A dead board that still answers identify has told you the die, the array size and, by implication, which memory map applies — the same information a debugger would give, obtained without a working clock.
The sixth row is the one to issue second, immediately after identify. It is a single transaction and it splits every incoming job into two categories.
How protection is enforced
Protection is not a single switch. It is applied per command, and the pattern is consistent.
| command | protection unset | protection level one | protection level two |
|---|---|---|---|
| identify | works | works | works, but the debug path is gone |
| read | works | refused | refused |
| write to unprotected region | works | works | works |
| mass erase | works | works, and clears the array | works, and clears the array |
| read protection status | reports unset | reports set | reports set |
| jump to application | works | works | works |
The asymmetry in the second and fourth rows is the whole story of this class of job. The loader will happily destroy the array on a protected device and will refuse to show it, which means the one command a protected device still accepts is the one that makes recovery impossible.
| what the loader says | what it means | what follows |
|---|---|---|
| read succeeds | protection is unset | read the array and finish |
| read refused, erase accepted | level one | the flash contents are not obtainable through this path |
| read refused, identify works | level one or two | distinguish by attempting a debug attach |
| no response at all | the loader is not being entered | check the pins, the interface and the supply before concluding the device is dead |
The one-word test
The most efficient habit in this work is to attempt a read of a single word immediately after identify, before anything else is discussed. It costs one transaction and it converts an open question into a decided one.
A successful single-word read establishes that the entire array is readable through the same path, because the protection check is per request rather than per region. A refusal establishes that no read command will work at any address, which removes an entire category of plan from consideration and points the work at the debug port and, if that is also closed, at physical access.
What it does not establish is whether the refusal was caused by protection or by a malformed request. The two produce the same negative acknowledgement on the wire, which is why the protection status command exists and why issuing it second is worth the extra transaction.
What the ROM path cannot do
Four things, each of which is proposed regularly as a workaround.
It cannot read a protected array, at any address, by any arrangement of commands. It cannot bypass the protection by starting a write and reading back the result, because a write to a protected region is refused before it reaches the array. It cannot be replaced with a modified loader, because the region is read-only by construction. And it cannot be made to run user code with the protection temporarily lifted, because there is no such state.
The one thing it can do that is sometimes overlooked is execute a jump to an address outside flash. A device whose application has been damaged can be given control of a small routine placed in RAM, and that routine can do what the loader will not — provided the protection permits reading the array at all. This is a narrow path and it depends entirely on the first classification; where protection is unset, the loader’s own read command is simpler and faster.
Where this fits in a job
The order is fixed. Enter the loader if the pins allow it, identify the device, read the protection status, attempt a one-word read, and only then plan the work. A job that starts at the debug port skips the cheapest diagnostic available, and a job that starts with physical work may be skipping two cheaper routes.
The member of the family with two USB controllers, where this loader is reachable over USB and where the descriptor tables provide the product’s own documentation, is in STM32F723 binary recovery. The protection levels described from the crypto side, including the case where a key is derived from the device identifier, are in STM32F756 code recovery, and the stripped member where the loader is often the only interface the product exposes is in STM32F722 code recovery.
FAQ
Does entering the loader erase anything? No. It runs from read-only memory and waits for commands; only the erase command changes the array.
Can the loader read a device protected at level one? No, and there is no sequence of commands that changes that. Level one also blocks the debug port.
Why does the loader still accept an erase on a protected device? Because erasing is how a protected device is returned to service, and the array is destroyed in the process. That is the intended behaviour and the opposite of a recovery.
Conclusion
The factory loader is the cheapest diagnostic instrument on the board: it runs without the application, it reports the die and the array size, and it classifies protection in two short transactions. Its refusal to read is not an obstacle to be worked around but a fact that redirects the job, and knowing that before quoting is worth more than any single piece of equipment.
STM32F765: The RAM Map That Does Not Follow the Pattern
Every memory map is a set of conventions, and developers learn them early: flash at the bottom, RAM above it, peripherals higher up. The conventions are useful right up to the point where a family arranges itself differently, and then the tooling — which is built on the conventions — quietly produces output in the wrong shape.
This member of the family is a good example. It has more RAM than its smaller siblings and it distributes that RAM differently, with the result that the address a developer expects to be the start of general-purpose SRAM is actually the start of something with different behaviour entirely.
The map as it actually is
Four regions matter, and they are not interchangeable.
| region | size on this part | base | behaviour |
|---|---|---|---|
| instruction tightly coupled memory | 16 KB | address zero | zero wait state, intended for routines with hard timing |
| data tightly coupled memory | 128 KB | the usual RAM base | zero wait state, intended for variables accessed in tight loops |
| general-purpose SRAM, first block | 368 KB | 128 KB above the RAM base | cached, DMA-accessible, the normal working memory |
| general-purpose SRAM, second block | 16 KB | far above the first block | cached, contiguous, often used for a separate purpose |
The second row is the one that breaks assumptions. On the smaller members of this family the tightly coupled data memory is 64 KB, and on this one it is 128 KB — which means the general-purpose SRAM starts 128 KB above the base rather than 64 KB above it. A tool told to read half a megabyte starting at the RAM base will read the right bytes and label them with the wrong region names for everything after the first 128 KB.
| what a flat read assumes | what is true here | the consequence |
|---|---|---|
| RAM is one contiguous block | four blocks with two different behaviours | region boundaries in the report are wrong |
| the start of RAM is general-purpose | the start of RAM is tightly coupled memory | DMA buffers attributed to a region DMA cannot reach |
| 16 KB of instruction memory sits beside the data memory | it sits at address zero, overlapping the flash alias | two different things appear at the same address |
| the RAM total is a single number to quote | the number is a sum of parts with different properties | the quoted figure is right and the description is wrong |
Why the labelling error survives review
An extraction that mislabels regions still produces plausible output. The bytes are correct, the lengths are correct, and the file opens. What is wrong is the interpretation, and interpretation errors are invisible in a hex dump.
The error surfaces later, usually in one of three ways. A customer tries to relocate a buffer into the region the report called general-purpose SRAM and the transfer stops working, because that region is tightly coupled and not reachable by the DMA engine. A customer builds a linker script from the reported sizes and the image no longer links, because the contiguous span the report implied does not exist. Or a customer compares the recovered RAM image against a memory dump taken with their own tool and finds a 128 KB displacement, concluding that one of the two reads is broken.
| downstream use | how the mislabelling appears |
|---|---|
| relocating a DMA buffer | transfers stall or land at the wrong address |
| writing a linker script | link fails, or code runs but at the wrong speed |
| comparing against another tool’s dump | a constant displacement between the two, blamed on the tools |
| performance work | a routine expected to be zero-wait turns out to be cached |
The alias at address zero
The instruction tightly coupled memory sits at address zero, and so does the alias of the flash array. Both are reachable at the same address, and which one answers depends on configuration established at reset.
This produces one of the more confusing observations in a live read: a read at a low address returns code before the program has relocated anything, and returns the contents of instruction memory afterwards. Neither reading is wrong. The address is simply served by two different things depending on how the device was set up, and a report that describes it as “the flash base” without qualification is describing only one of the two cases.
The practical rule is to determine the configuration before reading, not after. A boot stage that copies a routine into instruction memory and jumps to it will have left the evidence: a copy loop with a destination in the low address range, a branch afterwards, and the same bytes present twice. Seeing the duplicate is the confirmation that the alias was remapped.
What the linker script tells you
The most compact description of a device’s memory map is the linker script used to build its firmware, and a recovered image usually contains enough of one to reconstruct the intent.
| what appears in the image | what it implies |
|---|---|
| a copy loop whose destination is the RAM base | a block of initialised data is being moved into tightly coupled memory |
| a copy loop whose destination is a low address | a routine is being placed in instruction memory |
| an initial stack pointer near the top of the first block | the stack lives in general-purpose SRAM, not in the tightly coupled region |
| a vector table with the reset handler in flash but hot routines elsewhere | a deliberate split between slow and zero-wait execution |
The third row is a useful cross-check on the whole map. The initial stack pointer is the first word of the vector table and it is written by the linker, so it encodes the region the developer chose for the stack. If it lands inside the tightly coupled region, the design deliberately traded DMA reach for speed; if it lands at the top of the general-purpose block, the stack is where the conventions say it should be.
Reading a device that executes from tightly coupled memory
A device with routines in tightly coupled memory presents a specific problem for extraction: the code that matters is not in flash. It was copied there at boot, and the copy exists only while the device is powered.
That means a flash-only recovery returns the source of the copy — which is fine, because the copy is deterministic — but a RAM recovery taken at the wrong moment returns a half-populated region. The window between the copy starting and the jump that follows it is short, and a read taken inside that window contains a mixture of new and old contents that will not reproduce.
The safe approach is to read RAM only after the device has reached a stable state, identified by the program’s own signals: a completed initialisation flag, a steady heartbeat counter, or a peripheral that only runs after startup finishes. Reading early is faster and produces an image that cannot be defended.
What the report should contain
The report should give the map as four named regions with bases and sizes, state which of them are DMA-reachable, and say explicitly which region the stack occupies. It should also note whether anything was executing from tightly coupled memory at the time of the read, because that changes what the RAM image represents.
The banked flash arrangement of the larger members, where the boundary falls in the middle of the array, is covered in STM32F767 firmware recovery. The cache behaviour that decides whether a read of general-purpose SRAM is fresh is in STM32F746 software recovery, and the smallest member of the family, where the same map is compressed into fewer regions, is in STM32F722 code recovery.
FAQ
Is the tightly coupled memory usable as ordinary RAM? It is usable and it is fast, but it is not reachable by the DMA engine, which is the reason designs keep buffers out of it.
Does the 128 KB figure include the instruction memory? No. The two are separate, and the instruction memory is at address zero rather than beside the data memory.
Why does a read at a low address return code? Because the flash array is aliased there at reset, and instruction memory occupies the same address once the device configures it that way.
Conclusion
Four regions, two behaviours and one alias: the map on this part rewards reading the reference manual before reading the device. A flat dump is not wrong, but the labels attached to it decide whether the customer can use it, and the most expensive errors here are the ones that look like correct output.
STM32F756: What a Hardware Crypto Block Leaves Behind
A crypto block creates a particular kind of expectation in a recovery request. The customer knows the product uses encryption, concludes that the firmware is encrypted, and asks for the decryption to be undone. The expectation is reasonable and it is usually wrong, because encryption on a microcontroller is rarely applied to the code. It is applied to data in transit, to stored credentials, or to a firmware image that arrives through an update channel — and in the third case the decrypted result is already sitting in flash, because that is where the processor executes from.
So the useful question is not “how do I decrypt this”. It is “which of these three things is the crypto block doing here”, and the image answers that question.
What the block can and cannot do
The accelerator handles symmetric encryption in several modes, a set of hash functions, and a true random number generator. It is a peripheral: software writes a key into it, writes data, and reads results. It does not fetch code, it does not sit between the processor and flash, and it has no ability to decrypt instructions as they are fetched.
That last point is the one worth stating explicitly, because it is the source of most wasted effort in this class of job. There is no bus encryption on this part. Code executing from flash is code in the clear unless the software itself decrypted it before writing it there.
| the accelerator does | the accelerator does not |
|---|---|
| encrypt and decrypt buffers on request | decrypt code as it is fetched |
| compute hashes over supplied data | authenticate the flash array at boot by itself |
| generate random numbers | store or protect a key on its own |
| run in several chained modes | hide its own configuration from a memory read |
The right column is why a decrypted image is the normal case. An application that receives an update over a network decrypts it into RAM, verifies a hash, and programs the result into flash. By the time the product ships, the ciphertext is gone and the plaintext is what a recovery reads.
Four places a key can come from
The key is the part that is genuinely absent from a dump, and how it was provisioned determines whether the recovered image is reusable at all.
| provenance | how it appears in the image | what happens on another device |
|---|---|---|
| compiled into the code as a constant | a fixed byte sequence, often obfuscated by a byte-wise transform | the image works, and the key is recoverable |
| stored in a dedicated one-time-programmable area | a read of that area returns the key or a fingerprint of it | the image works if the new device is provisioned identically |
| derived from the device’s unique identifier | arithmetic over a value read from system memory | the image runs but produces wrong results, or fails its own check |
| entered from outside at commissioning and never stored | nothing at all | nothing can be restored; the device is a brick without the key |
The third row is the one that produces the most confusing customer reports, and it is covered from the arithmetic side below. The fourth row is worth recognising early because it changes the commercial conversation: if the key is genuinely absent from every readable location, no amount of extraction will make the product work again, and saying so before the work starts is more valuable than a perfect dump delivered with an apology.
What the image keeps after the key is gone
Even when the key is unreadable, the surrounding code is highly informative, and it is where a recovery earns its value.
| artefact | what it looks like | what it tells you |
|---|---|---|
| the key-loading sequence | a short block writing consecutive words to one peripheral register block | which key slot and length the design used |
| mode and chaining setup | writes that set a mode field and an initialisation vector | whether the product used authenticated modes |
| a hash comparison | two buffers compared word by word after a hash completes | the program verifies integrity, so a modified image will be rejected |
| the storage format | a header with a length, a nonce and a tag before each block | how the stored data is structured, and therefore how to regenerate it |
| a random-number read at startup | a register read very early, feeding a buffer | where session keys or nonces come from |
The third row has a practical consequence that is easy to miss. A product that verifies a hash over its own flash at boot will refuse to run a restored image that differs by a single byte. Restoring such a device requires reproducing the exact original image, not merely a functional one, and the verification routine is the specification for what “exact” means.
The two protection levels
Protection on this family is set in option bytes and has two meaningful states on the debug side, with very different consequences.
| level | what it does | what it means for a recovery |
|---|---|---|
| unset | debug port works, memory readable, flash writable | straightforward read |
| level one | debug port is restricted, memory reads over the debug interface are blocked, but the application still runs and the flash can be mass-erased | the debug path is closed; erasing is possible but destroys the contents, so it is not a recovery |
| level two | the debug port is permanently disabled, and no mechanism exists to re-enable it | the die has to be accessed physically; there is no software route back |
The middle row describes the situation most often misreported as “irreversibly locked”. It is not irreversible — the device can be erased and reused — but erasing is the opposite of recovering, because on a flash part the erase that clears the protection also clears the array. That equivalence holds here exactly as it does on much smaller parts, and it is worth stating in plain language before a customer approves destructive work.
Why a working image can fail on a second device
Consider the case where the key is derived from the unique identifier. The derivation is short: a few words of identifier, a mixing step, and the result used as a key. Nothing about it is visible unless you look for it, and when the image is restored onto a second device the failure is not a crypto error message. It is a corrupted data structure, or a communication stack that authenticates nothing, or a device that boots and then refuses to accept its own configuration.
The pattern is recognisable: the image contains a read from the system memory area very early, followed by arithmetic that produces a value used as a key, and a checksum that will not match on any other die. A report that identifies this pattern tells the customer that the recovery is valid for one device and that a fleet cannot be restored from it, which is a materially different answer from “recovered successfully”.
| symptom after restore on a second device | likely cause |
|---|---|
| boots, then fails an internal integrity check | identifier-derived key or a hash bound to the original die |
| boots, runs, but produces wrong output values | same, with the failure absorbed by the application |
| refuses to enter its normal mode | a provisioning check against a stored fingerprint |
| works completely | the key was a constant or the provisioning was reproduced |
What the report should state
State whether the image contains ciphertext at all, and if so where. State the key provenance with the evidence for the conclusion. State the protection level that was found, and if it was level two, say so before quoting physical work. And state whether the recovered image is portable to another device, because that single sentence decides whether the customer can use the result for production or only for analysis.
The value-line member of the same family, where the internal array is small enough that the application body is elsewhere, is covered in STM32F750 flash recovery. The protection and boot-path interaction is set out in STM32F745 heximal extraction, and the identification step that must precede any of this is in STM32F747 binary recovery. A comparable crypto-equipped case on an older core is at STM32F407ZG firmware restoration.
FAQ
Is the firmware itself encrypted on this part? Almost never. The processor fetches instructions directly from flash, so the code has to be in the clear. Encryption applies to data and to update packages.
Can the crypto block be used to hide code? Only by decrypting it at runtime into RAM, which leaves a decrypted copy in RAM while the device runs.
Does level two protection mean the die is unusable? No, but it means no debug path exists. Physical access is the only route, and the die is destroyed as a functional part in the process.
Conclusion
A crypto block does not hide code; it processes data. The key is the only genuinely missing piece, and its provenance — constant, one-time area, identifier-derived, or supplied from outside — decides whether a recovered image is portable, single-device, or worthless. Reporting that provenance, together with the protection level actually found, is the difference between a dump and an answer.
ATmega164 Boot Section: SPM and the Bits That Guard It
An AVR that can update its own firmware must be able to write to its own flash. The instruction that does it is SPM, store program memory, and its existence changes the shape of the protection problem: there are now two containers — the application section and the boot section — and the boot section is the one that holds the update logic.
Most discussions of AVR protection treat the lock bits as a single wall. On a part with a bootloader there are two walls, and they are built from different bit fields with different rules. Which wall is up determines what a read can see, and it is common to find one up and the other down.
Two sections, two jobs
The flash on this family is divided into a read-while-write section and a no-read-while-write section. The boot section sits at the top of the address space and is intended to hold code that must not be overwritten by the code it is updating.
The boot section has three properties that matter:
- It can be entered automatically on reset if the boot reset fuse is programmed.
- Its size is selected by a fuse pair, in fixed steps.
- It is the only place from which
SPMmay write, on parts where the SPM instruction is restricted to the boot section.
That last point is the crux. The application code cannot rewrite flash by itself; it has to hand control to the boot section, which performs the write and returns. The boot section is therefore privileged code, and the hardware gives it its own protection bits.
The boot lock bits are not the lock bits
| Field | Guards | Typical effect when set |
|---|---|---|
| General lock bits | Whole flash, both sections | Blocks programming, then verification |
| Boot lock bits, mode 1 | Application writing into boot section | Application cannot modify the bootloader |
| Boot lock bits, mode 2 | Boot section writing into itself | Bootloader cannot self-modify |
| Boot lock bits, mode 3 | Both of the above | Bootloader is read-only to software |
Two facts follow, and both are useful on the bench.
First, a part can have its general lock bits clear — flash readable — and still have boot lock mode 2 set, which blocks the boot section from rewriting itself. That combination is common in products that want a field-updatable application but an immutable bootloader.
Second, the boot lock bits do not block external reads. They govern what software running on the chip may write. External read permission comes from the general lock bits alone.
Why a bootloader is a second door
If the boot section is present and its lock bits allow it, the device can program itself. That means a device in the field with a working bootloader accepts new code over whatever transport the bootloader implements — UART, USB, CAN, radio.
For anyone examining a product, that is a completely different situation from a device with no bootloader. There is an existing, vendor-supported path to change the contents. The question stops being “how do I get in” and becomes “what does the bootloader accept, and what does it check”. Signature verification in the bootloader turns an open door into a locked one, and the presence or absence of a verification step is often the single most important thing to determine about the product.
The erase question again
SPM can write flash, and it can also perform a page erase. It cannot erase the whole chip, and it cannot change the lock bits. That asymmetry is deliberate: a bootloader must be able to update a page, and must not be able to remove its own protection.
So on a part whose protection is the general lock bits in mode 2 or 3, the bootloader is not an escape route. It can rewrite the application section; it cannot unlock anything. The only operation that clears the lock bits remains the external chip erase, which returns the flash to blank.
That is worth stating clearly because the intuition runs the other way — “there’s a bootloader, so there’s a way in”. There is a way to write, not a way to read.
Reading the boot section specifically
If the general lock bits permit verification, both sections read normally, and the boot section is just the top slice of the address space. Its contents are frequently the most informative part of the image, because bootloaders tend to contain readable strings: protocol names, version numbers, error messages, sometimes vendor URLs.
If the general lock bits do not permit verification, neither section reads. The distinction between the two containers only becomes decisive when the general bits are open and the boot bits are not — in which case the application reads, the boot section reads, but the on-chip software cannot touch the bootloader.
What a fuse read tells you before any of this
Before assuming anything about sections, read the fuses. Three of them set the shape of the problem:
- Boot reset fuse: whether reset vectors into the boot section at all.
- Boot size fuse pair: how large the boot section is, in fixed steps.
- Boot lock bits: which of the three modes applies.
A part with the boot reset fuse unprogrammed still has a boot section defined, but never enters it. A part with the boot size fuse at its smallest setting has a boot section that can hold a jump table and very little else. Both are useful facts and both come from a read that does not touch the flash.
Related notes
- ATmega644 and read-while-write behaviour — what the other half of the split does under load.
- ATmega324PA and brown-out reads — why a marginal read looks like corruption.
- ATmega644PA and RSTDISBL — the fuse that removes ISP entry entirely.
FAQ
Can a bootloader remove the lock bits? No. SPM cannot modify the lock bits. Only an external chip erase resets them, and it erases the flash as well.
Does a locked part still boot through its bootloader? Yes. Lock bits are enforced by the programming interface, not by the CPU. The device runs exactly as designed.
Are the boot lock bits readable? Yes, the lock byte includes them and can be read back through the programming interface.
If the boot section is locked, is the application section still readable? That depends on the general lock bits, not the boot bits. The two fields are independent.
Conclusion
On an ATmega164 with a bootloader, protection is two containers with two bit fields. The boot lock bits govern what on-chip software may write; the general lock bits govern what an external programmer may read. Confusing the two leads to wrong conclusions in both directions — assuming a bootloader means access, and assuming a locked boot section means a locked device.
ATmega861A: Telling Library Code From Yours in Eight Kilobytes
Every compiled image contains two kinds of code: what the developer wrote and what the toolchain supplied. At eight kilobytes both are present in quantity, and the ability to tell them apart is what makes an analysis tractable — because one half can be skimmed and the other cannot.
The boundary is not marked. It has to be inferred, and there are reliable signals.
Signals that identify library code
Repetitive structure. Multiplication, division, and long-integer arithmetic compile into tight loops with a characteristic shape. Two such loops in an image usually mean the same routine was emitted twice, or that one is multiply and one is divide.
No peripheral access. Library routines touch memory, not registers. A function whose body contains no write to a hardware register address is almost certainly compiler-generated.
Called from many places, calls nothing. A leaf function with many callers and no callees is either a utility or a hardware abstraction the developer wrote. Its body distinguishes the two.
Fixed, recognisable prologues. Saving and restoring registers around a computation produces a repeated pattern at entry and exit, which is a strong hint that the body was generated rather than written.
Signals that identify application code
The complement is easier to spot:
- Writes to peripheral registers with addresses in the hardware range.
- Reads of status flags followed by conditional branches.
- Delay loops sized with round numbers.
- Table lookups whose index comes from an analogue reading.
That last item is where the product usually is. A table indexed by an ADC result means a calibration curve, a display segment map, or a threshold set — and all three are design decisions rather than mechanical translation.
| Signal | Library | Application |
|---|---|---|
| Register writes | none | frequent |
| Flag polling | rare | constant |
| Table lookups | no | common |
| Register save/restore | yes | only if large |
Why the distinction saves time
An image at this size may contain a kilobyte of arithmetic support. Reading it instruction by instruction is wasted effort; recognising it as “16-bit divide” and moving on is the correct level of attention.
Conversely, treating application code as library code is the expensive error, because the application is where the design lives and where the constants are. Skimming it produces a report that describes the compiler rather than the product.
The discipline is therefore: classify first, then read selectively. Classification is a matter of scanning for register access, which is fast and reliable.
The A revision in this context
The revision affects the die, not the toolchain. It may change the signature byte, and it may change how a peripheral behaves at the edge of its specification, which in turn changes whether a workaround appears in the application half.
That is the useful connection: when comparing two images of the same product built for two revisions, the differences concentrate in the workarounds. Library code is identical; application code differs in exactly the places the die changed.
Reading order
- Signature, fuses, lock byte.
- Array read, verified.
- Reset vector, then initialisation.
- Classify functions by register access — library or application.
- Extract constants from the application half, noting scale factors.
Step 5 is the deliverable. Constants without their scale factors are numbers, and numbers without context are the usual reason a reconstruction fails on first build.
Related notes
- ATmega261A and the two-kilobyte case — the same analysis where the library half is absent.
- ATmega261 and differential reading — comparing two builds.
- ATtiny861 and address spaces — where the tables these routines use are stored.
FAQ
Can the boundary be found automatically? Partly. Register-access scanning finds most application code; the rest needs judgement.
Is library code worth reading at all? Only to identify it. Its behaviour is known.
Why do the same routines appear twice? Separate translation units or an inlined variant beside the out-of-line one.
Does the revision change the library half? No. The compiler output is the same; only the application’s workarounds differ.
Conclusion
Eight kilobytes is the size at which an image stops being readable as a whole and starts needing classification. Register access is the discriminator, and applying it first turns a long list of functions into two short lists, one of which can be skimmed and one of which is the product.