The deliverable customers interact with most often is the one most commonly treated as a secondary artifact. Six document types, six audiences, and one release gate that fixes most of the failure modes.
A semiconductor SDK can have an exemplary architecture, a thoughtfully versioned C API, a security framework that maps cleanly to PSA Certified Level 2, and an OTA pipeline that has been fault-injected against every meaningful failure mode — and still lose design-wins to a competitor whose documentation makes the developer journey predictable and fast. The reason is that documentation is the surface a customer interacts with first, longest, and most often, and the surface where a poor experience is least recoverable. A developer who reaches a confusing reference page on a Friday afternoon does not file a ticket. They go evaluate something else.
The fix is not to write more documentation. It is to treat documentation as a product. That means owning it inside the engineering team, reviewing it on every pull request, gating it into the release process as a mandatory artifact, and applying the same versioning, deprecation, and release discipline to it that the C API enjoys. Documentation written this way does not need to be longer or more elaborate than the alternative. It needs to be reliable, indexable, and structurally aligned with how developers actually progress through the SDK.
This post walks through the six types of documentation an SDK has to ship, the audience each one is written for, the specific quality standards that distinguish documentation that gets used from documentation that gets bypassed, and the release-gate discipline that keeps it consistent over time.
Six types, six audiences
A modern SDK ships at least six distinct document types, each with a different reader and a different success criterion. Conflating them — or treating them as variations of a single artifact — is the most common cause of documentation that seems comprehensive in aggregate and unusable in practice.
The API reference is for developers and architects, in the moment they are writing code that calls a specific function and need to know its contract. The success criterion is that the developer finds the function, reads its parameter and return semantics, and writes a correct call without having to read anything else.
Technical guides are for developers and architects working at one layer of abstraction above the API reference. They cover how subsystems fit together: how to integrate the BLE stack with a Thread network, how to configure the secure boot chain, how to use DMA with the UART driver. The success criterion is that a developer who has skimmed the guide can write a working integration without the trial-and-error of treating the API reference as a discovery tool.
The getting-started guide is for the broadest audience: developers, architects, product owners, and project managers, in the moment they are deciding whether the SDK is worth a deeper look. The success criterion is a running firmware image on the lowest-cost supported hardware target, in under thirty minutes. A developer who fails here does not read further. This is the highest-impact page in the entire documentation set.
Sample / example READMEs are for developers, architects, and technical product owners who are using a sample application as their starting point — which, in practice, most of them are. The success criterion is that a developer can take the sample, wire up the hardware described in the README, run the application, see the expected output, and modify it for their own use case.
The changelog is for existing SDK users deciding whether to adopt a new release. The success criterion is that they can read it in two minutes and know whether the new release contains anything they need, anything that breaks them, or anything that requires a configuration change.
Migration guides are for existing users actively upgrading. The success criterion is a deterministic step-by-step path from the version they are on to the version they are moving to, with every breaking change explicitly addressed.
These six audiences are not the same person at different moments. They are different roles, evaluating the SDK against different success criteria. Documentation that does not separate them produces pages that try to serve all of them and end up serving none.

The API reference: document the contract, not the name
The API reference is where the most documentation by volume lives, and where the most common failure mode is the most subtle: writing comments that restate the function name instead of specifying the function contract.
A comment that says “Initialises the UART” for a function called hal_uart_init() is not documentation. It is the function name written in a different font. The developer who needed the documentation needed to know what state the peripheral is in after the call, what error codes it can return, whether it can be called from interrupt context, whether it is thread-safe, and what calls it has to be paired with. A useful API comment specifies those things.
The standard tool for this in the embedded SDK world is Doxygen. A complete Doxygen block for a single function looks like this:
/**
* @brief Initialise a UART peripheral instance.
*
* Configures the UART hardware according to the parameters in @p h->Init
* and calls HAL_UART_MspInit() to configure the associated GPIO and clock.
* The peripheral is left in READY state on success.
*
* @param[in,out] h Pointer to a caller-allocated UART handle. Must not be
* NULL. The handle is owned by the caller for its lifetime;
* do not modify it while a transfer is active.
*
* @retval HAL_OK Peripheral initialised successfully.
* @retval HAL_ERROR Hardware fault during init (check h->ErrorCode).
* @retval HAL_TIMEOUT Clock enabling did not complete within 1 ms.
*
* @note This function must not be called from an ISR context.
* @note Thread-safe: NO. Concurrent calls on the same handle produce
* undefined behaviour. Use a mutex if init may race with transfers.
*
* @see HAL_UART_DeInit(), HAL_UART_Transmit(), HAL_UART_Receive_DMA()
*/
HAL_StatusTypeDef HAL_UART_Init(UART_HandleDef_t *h);
Every public Doxygen block should carry the same set of fields. A one-line @brief. A @param for every parameter, with a direction tag — [in], [out], or [in,out] — that tells the caller whether they own the memory before or after. A @retval for every possible return value, not just the success case. @note blocks for ISR safety and thread safety, treated as contractual rather than advisory. A @see block linking to related functions, so a developer who lands on HAL_UART_Init can navigate to HAL_UART_DeInit without searching.
The reason this discipline matters is that the API reference is the documentation a developer reads in the moment they are writing the bug. If the contract is not in the comment, the developer either guesses — which is how the bug ends up in production — or reads the source, which is what the API reference exists to prevent.
The mechanism that holds this standard in place is generating the Doxygen output as part of every CI build, and failing the build on any Doxygen warning. A missing @param, an undocumented return value, an unmatched @see reference — each of these triggers a warning, and treating warnings as errors is the only enforcement that survives organisational change. Documentation discipline that depends on individual reviewers remembering to check it does not survive the first quarter where someone is rushing to ship.
Conceptual documentation as a journey
Beyond the API reference, the conceptual documentation has to follow a hierarchy that reflects how developers progressively engage with the SDK — from orientation, through integration, to deep reference material. A developer navigating the documentation for the first time is asking progressively deeper questions: what is this, how do I get started, how do I use a specific peripheral, how do I integrate an RTOS, and how do I migrate from the previous version. The structure has to reflect that journey.
A top-level structure should include:
An introduction — what the SDK is, what hardware it targets, where to obtain it, and how to file bugs. Concise enough that a developer can determine in a minute whether the SDK supports their hardware and use case.
An architecture overview — the layered stack diagram, component dependencies, and a guided tour of the repository structure. The foundational context that all subsequent documentation builds on. A developer who has read the architecture overview can interpret every other page; a developer who has not is reading every page in isolation.
A getting-started guide — a minimal working example that runs on the lowest-cost supported hardware target, achievable in under thirty minutes. This is the page the entire documentation set rises or falls on. A developer who succeeds here is now invested. A developer who fails here is gone.
Peripheral guides — one per supported peripheral type, covering HAL usage, DMA configuration, and common integration pitfalls. The peripheral guide is the natural reading after the getting-started, because the developer has now seen that the SDK works and is asking what else they can do with it.
An RTOS integration guide — how to configure the SDK for each supported RTOS, with worked examples of task creation, mutex usage, and ISR-safe API calls. Particularly important for SDKs that support multiple RTOSes, because the customer’s choice of RTOS is often made before they evaluate the SDK, and they need to confirm that their RTOS is a first-class target.
A security guide — TF-M configuration, PSA Certified targeting, secure boot chain setup, and OTA pipeline integration. For customers shipping into regulated markets, this guide is part of their compliance documentation, not an optional read.
Migration guides — indexed by source and target SDK version, published at the time of the breaking change, not after. A migration guide that arrives a release later is a migration guide that customers had to write themselves.
The API reference — auto-generated from Doxygen, cross-linked from every conceptual guide so a developer reading the UART peripheral guide can move directly to the function contract and back.
The hierarchy is not arbitrary. It mirrors the developer journey, and the documentation that follows it is the documentation that gets used end-to-end, rather than landed-on through search and abandoned.

Sample code is the most-consumed documentation
Sample applications are the most consumed form of SDK documentation, and the form most commonly held to a lower quality standard than the rest. A user evaluating an SDK may look at a sample application before reading any of the included guides — sometimes before reading any documentation at all. The sample is, for many evaluations, the documentation.
This implies that sample code has to be held to the same quality standard as production library code: it has to compile cleanly without warnings at the maximum warning level, pass static analysis, be tested in CI on real hardware, and be written in a style that demonstrates best practices rather than minimum viable functionality. A sample that “works” but uses deprecated APIs, or that hardcodes pin assignments that should be in the device tree, or that ignores return codes, is teaching the customer to write the wrong code.
Each sample has to ship with a co-located README that specifies a brief description of what the sample demonstrates, the prerequisites to run it, a wiring diagram or pin table for any external components, the expected serial output, and a troubleshooting section covering the three most common failure modes observed during internal testing. The troubleshooting section is the part most teams skip and the part customers most rely on. A sample that fails for a customer is a customer who is now searching the issue tracker; a sample whose README anticipates the failure is a customer who fixes their setup in two minutes and continues evaluating.
The changelog as the highest-leverage page
The changelog is the primary communication channel between the SDK team and its developer community, and it is the page existing customers read first on every release. It has to follow a machine-parseable format — the Keep a Changelog convention is the de facto standard — and live in the repository root, not in a documentation site that requires a separate browser tab.
Every entry has to be categorised by impact on the user. The standard categories are: Added for new features, Changed for behaviour changes that are not breaking, Deprecated for symbols that will be removed in a future major version, Removed for breaking removals, Fixed for bug fixes, and Security for security vulnerabilities addressed.
The categorisation is what makes the changelog scannable. A customer who needs to know whether a release contains a security fix reads the Security section and stops. A customer evaluating whether a release is breaking reads the Removed and Changed sections. A customer who is just curious reads the Added section. A flat changelog without categories forces every customer to read every entry to extract the few that matter to them, and the practical result is that they read none.
The release gate that holds it all together
Documentation discipline that depends on individual contributors remembering the standard does not survive contact with deadlines. The discipline that survives is mechanical, and it lives in the release pipeline.
Three checks, run on every pull request and every release candidate, hold most of the standard in place. Documentation builds — the Doxygen run, the static-site generator that produces the conceptual guides, the changelog parser that validates the format — must run as part of CI, and any warning has to fail the build. Sample applications must compile and run in CI, on real hardware, on every release candidate, with their READMEs validated against the actual output. Migration guides must be present in the repository at the moment a breaking change is merged, not added later.
These checks are not exotic. They are the same kind of CI discipline that the rest of the SDK depends on, applied to documentation as a peer artifact rather than a downstream one. The cost is mostly process — adding a Doxygen step to CI, requiring a sample-README check on PRs that touch samples, blocking merges of breaking changes that have no migration guide entry. The benefit is that the documentation stops drifting from the code.

Why this matters for adoption
An SDK with exemplary architecture and inadequate documentation may lose design-wins to a competitor whose documentation makes the developer journey predictable and fast. The point is not that documentation is more important than architecture. It is that documentation is the surface customers touch in every interaction with the SDK, including the first one, and a poor experience there is the one most likely to end the evaluation before the architecture has a chance to demonstrate itself.
Documentation as a product means it has an owner, a release cadence, a quality bar enforced by CI, a changelog that records its own evolution, and a versioning policy that treats it as part of the public surface. Done that way, it costs roughly what the rest of the SDK costs to maintain — a small fraction of the engineering team’s time, sustained over the platform’s lifetime — and it pays back as the difference between customers who adopt the SDK and customers who abandon the evaluation.
The temptation, on every release, is to defer the documentation work because the code is shipping and the docs can catch up. They never do. The documentation that catches up is the documentation written under deadline, with the code already merged and the original context already fading, and that is the documentation that produces the second-class experience customers route around. The discipline is to treat the documentation as part of the release, not as the thing that follows it.
Documentation that’s as solid as your architecture?
needCode builds embedded SDKs where the documentation is part of the release, not the thing that follows it. If you’re evaluating SDK partners for a connected product deployment, let’s talk.
Book a free discovery call or get in touch
Further reading
Anatomy of a Production OTA Pipeline — a worked example of the SDK subsystem this article’s documentation framework is built to describe
Firmware Security — how needCode approaches secure boot, PSA Certified targeting, and the security guide deliverables covered in this post
FreeRTOS / Zephyr OS — the RTOS integration documentation in practice, covering task creation, mutex usage, and ISR-safe API patterns across multiple RTOS targets
End-to-End Testing — the CI infrastructure behind sample validation on real hardware and the automated checks that keep documentation consistent with code

