All You Ever Needed To Know About UPDI, And Then Some!

This entry is part 2 of 2 in the series IMPLEMENTING UPDI FROM SCRATCH

Table of Contents

IMPLEMENTING UPDI FROM SCRATCH

All You Ever Needed To Know About UPDI, And Then Some!

All You Ever Needed To Know About UPDI, And Then Some!

All You Ever Needed To Know About UPDI, And Then Some!

All You Ever Needed To Know About UPDI, And Then Some!

Post Stastics

  • This post has 8032 words.
  • Estimated read time is 38.25 minute(s).

Part II — Building UPDI from Scratch with an Arduino Nano

In Part I we took UPDI apart conceptually.

We established that UPDI is not merely “serial programming on one wire.” It is a layered system consisting of:

  • an electrical physical interface;
  • UART-like framing;
  • synchronization and BREAK mechanisms;
  • a compact UPDI instruction set;
  • memory-mapped access to the target;
  • protected activation mechanisms;
  • target-specific nonvolatile-memory controllers;
  • and an on-chip debugging system.

Now we are going to begin making it work.

Rather than immediately downloading somebody else’s programmer firmware and treating the Arduino Nano as a mysterious appliance, we will construct the essential portions ourselves.

Our goals in this part are to:

  1. select a convenient UPDI target;
  2. build the finalized Nano UPDI electrical interface;
  3. understand how one external UPDI wire becomes separate TX and RX paths inside the programmer;
  4. support target voltages below the Nano’s fixed 5 V logic level;
  5. generate UPDI signaling;
  6. transmit correctly formatted 8E2 serial frames;
  7. receive frames from the target;
  8. generate BREAK;
  9. perform ordinary UPDI activation;
  10. send SYNCH;
  11. construct actual UPDI instructions;
  12. read UPDI’s own status registers;
  13. reach the target’s memory-mapped address space;
  14. measure VTARGET and the external HV rail;
  15. develop a repeatable test methodology;
  16. and introduce a companion UPDI protocol analyzer.

The purpose is not to outperform Microchip’s professional tools.

Nor are we trying to outperform mature open-source projects.

We are trying to understand the interface deeply enough that:

source code
    =
timing diagrams
    =
oscilloscope traces
    =
actual UPDI transactions

all tell the same story.


45. First, a Correction About 225 kbaud

Many descriptions of UPDI leave the impression that it operates at:

225000 baud

as though that were a fixed UPDI baud rate.

It is not.

UPDI performs automatic baud synchronization from the:

0x55

SYNCH character transmitted at the beginning of each new instruction.

The target measures that incoming pattern and uses it to derive serial timing for the associated instruction and response.

There is therefore no ordinary initial UART baud-rate configuration transaction that the programmer must perform before the first communication.

For implementations using the traditional UPDI clock selections, representative maximum rates are approximately:

UPDI clockRepresentative maximum
4 MHz225 kbaud
8 MHz450 kbaud
16 MHz0.9 Mbaud
32 MHz on applicable newer devicesabout 1.8 Mbaud

The exact supported maximum depends upon:

  • target family;
  • target voltage;
  • UPDI clock configuration;
  • operating conditions;
  • and the device’s own electrical specification.

Our Arduino Nano does not need to begin anywhere near those limits.

A polished programmer benefits from speed.

An educational programmer benefits from visibility.

We can therefore start around:

62,500 baud

At 62,500 baud:

[ T_{BIT} = \frac{1}{62500}=16\ \mu s ]

Our ATmega328P-based Nano runs at:

16 MHz

so:

[ 16,MHz\times16,\mu s=256 ]

CPU clocks are available during each bit.

That gives us enormous timing headroom.

At 225 kbaud:

[ T_{BIT}\approx4.444\ \mu s ]

and:

[ 16,MHz\times4.444,\mu s\approx71.1 ]

CPU clocks are available per bit.

That is still practical for carefully written AVR firmware, but considerably less forgiving while learning and debugging the protocol.

Our development sequence will therefore remain:

slow and observable
        ↓
proven reliable
        ↓
faster
        ↓
qualified
        ↓
optimized

rather than:

maximum speed
        ↓
mysterious intermittent failures

46. Choosing Our Experimental Target

For the primary target in this article we use:

AVR16DU28-I/SP

This is particularly convenient for a breadboard-based UPDI tutorial because the /SP version is supplied in a:

28-pin SPDIP package

That means:

  • no breakout PCB is required;
  • no SMD soldering is required;
  • individual pins are readily accessible;
  • a logic analyzer or oscilloscope probe can be attached easily;
  • and the device can be replaced without reworking a board.

The AVR16DU28 provides approximately:

  • 16 KB Flash;
  • 2 KB SRAM;
  • 256 bytes EEPROM;
  • operation up to 24 MHz;
  • USB Full-Speed device capability;
  • SPI;
  • TWI/I²C;
  • USARTs;
  • ADC and other modern AVR peripherals;
  • and UPDI programming/debugging.

Its nominal operating supply range is:

1.8 V to 5.5 V

That makes it especially useful for testing the voltage-translation circuitry we have now added to our programmer.

Useful reference links

Microchip AVR16DU28:

https://www.microchip.com/en-us/product/avr16du28

DigiKey and Mouser can be searched using the exact ordering code:

AVR16DU28-I/SP

LCSC inventory varies, particularly for the through-hole package, so current availability should be checked rather than assumed from an old listing.

Datasheet

Our primary device document is Microchip’s current AVR DU family complete data sheet available from the AVR16DU28 product page.

Whenever this article and an old locally saved copy of a data sheet disagree:

use the current Microchip device documentation.

That is especially important for:

  • absolute maximum ratings;
  • HV activation;
  • fuses;
  • NVM behavior;
  • UPDI timing;
  • and silicon revisions.

46.1 Other Good UPDI Targets

The AVR16DU28-I/SP is not mandatory.

Two particularly useful alternatives are:

AVR32DU28-I/SP

This is the larger-memory sibling of our primary device.

It provides approximately:

32 KB Flash
4 KB SRAM
256 B EEPROM
24 MHz maximum CPU clock
28-pin SPDIP
1.8–5.5 V supply

and belongs to the same AVR DU family.

It is perhaps the easiest substitute for the AVR16DU28.


AVR16DD28-I/SP

This is another excellent breadboard target.

It provides approximately:

16 KB Flash
2 KB SRAM
256 B EEPROM
24 MHz maximum CPU clock
28-pin SPDIP
1.8–5.5 V supply

but belongs to the AVR DD family.

That makes it particularly educational because it lets us compare two modern UPDI families while keeping many gross characteristics similar.

The DD family also introduces features such as:

MVIO — Multi-Voltage I/O

which reinforce an important lesson:

Modern AVRs may operate in several voltage domains, so a serious programmer should not assume that its own 5 V logic level is automatically appropriate for the target.


46.2 Our Reference-Target Table

ParameterAVR16DU28-I/SPAVR32DU28-I/SPAVR16DD28-I/SP
FamilyAVR DUAVR DUAVR DD
Package28-SPDIP28-SPDIP28-SPDIP
Flash16 KB32 KB16 KB
SRAM2 KB4 KB2 KB
EEPROM256 B256 B256 B
Maximum CPU clock24 MHz24 MHz24 MHz
Supply range1.8–5.5 V1.8–5.5 V1.8–5.5 V
5 V compatibleYesYesYes
UPDIYesYesYes

The AVR16DD28 is deliberately included as a second-family reference.

We do not want to teach:

“The AVR16DU28 does this, therefore every UPDI AVR must do exactly this.”

That is one of the easiest ways to write a programmer that works beautifully on three devices and fails completely on the fourth.


47. From the Simplest UPDI Connection to Our Final Interface

The simplest same-voltage Arduino UPDI experiment can be built with approximately:

Nano D6 ---- 4.7 kΩ ---- target UPDI

when:

Nano VCC   ≈ 5 V
Target VDD ≈ 5 V

That configuration is historically useful and helps explain how projects such as jtag2updi can achieve UPDI with very little hardware.

But it is not the electrical interface we will use for the finalized article programmer.

Our final design is intended to be more useful.

It separates internal transmit and receive functions and electrically isolates the Nano’s 5 V GPIOs from the target UPDI voltage domain.

Externally:

TARGET
  |
  +---- one UPDI conductor

Internally:

Nano D6 = UPDI_TX
Nano D5 = UPDI_RX

The two paths meet only at the target-side UPDI node.


47.1 The Final Target Connector

Our target connector contains four signals:

1  TARGET_RESET
2  VTARGET
3  UPDI
4  GND

Conceptually:

NANO PROGRAMMER                      TARGET AVR

TARGET_RESET ---------------------- RESET
VTARGET --------------------------- VDD
UPDI ------------------------------ UPDI
GND ------------------------------- GND

For ordinary UPDI communication, RESET is not necessarily required.

We expose it because it is useful for:

  • controlled reset;
  • debugging;
  • automated testing;
  • HV recovery on applicable modern devices.

48. Why We Split TX and RX

A single GPIO can implement half-duplex UPDI by switching between:

OUTPUT

and:

INPUT / high impedance

states.

That works.

But our Nano is fixed at approximately:

5 V logic

while our target might operate at:

1.8 V
3.3 V
5.0 V
5.5 V

and an older shared-pin UPDI device may briefly receive approximately:

12 V

during HV activation.

A bare Nano GPIO is therefore not the ideal universal electrical endpoint.

Our final architecture uses:

D6 → TX transistor → target UPDI

target UPDI → RX transistor → D5

This gives us:

  • target-voltage-domain isolation;
  • 5 V Nano protection;
  • easier lower-VDD support;
  • old-style UPDI-HV tolerance;
  • permanent RX availability;
  • and much cleaner firmware direction handling.

49. The UPDI Transmit Stage

The TX path uses a 2N3904 as an open-collector pull-down.

Conceptually:

Nano D6 / UPDI_TX
        |
      47 kΩ
        |
       base
      QTX
    2N3904
       |
collector
       |
       +---------------- TARGET UPDI
       |
     emitter
       |
      GND

A base-to-ground resistor keeps QTX OFF when the Nano is resetting and D6 is high impedance.

In the finalized design this biasing is deliberately weak so that it does not waste useful drive current.

Logical operation

When:

D6 = HIGH

QTX turns ON.

Therefore:

TARGET UPDI = LOW

When:

D6 = LOW

QTX turns OFF.

Therefore:

TARGET UPDI = released

The Nano never actively drives target UPDI HIGH.

That is one of the major advantages of this circuit.

The target’s own voltage domain establishes the HIGH level.


50. The UPDI Receive Stage

The RX path also uses a 2N3904.

Conceptually:

TARGET UPDI
     |
   47 kΩ
     |
    base
     QRX
   2N3904
     |
   emitter
     |
    GND

QRX collector
     |
     +---------------- Nano D5 / UPDI_RX
     |
   47 kΩ
     |
    +5 V

When target UPDI becomes HIGH, QRX receives base current and pulls:

Nano D5 LOW

When target UPDI becomes LOW, QRX turns OFF and the Nano-side pull-up raises:

Nano D5 HIGH

Thus:

Target UPDINano UPDI_RX
LOWHIGH
HIGHLOW

The receive path is therefore:

inverted.


50.1 TX Is Inverted Too

The transmit stage is also logically inverted:

Nano UPDI_TXQTXTarget UPDI
LOWOFFReleased/HIGH
HIGHONLOW

This might look inconvenient at first.

In practice it is easy to hide behind a physical-layer abstraction.

For example:

inline void updiDriveLow()
{
    // HIGH turns the NPN TX transistor on.
    PORTD |= _BV(PD6);
}

inline void updiRelease()
{
    // LOW turns the NPN TX transistor off.
    PORTD &= ~_BV(PD6);
}

inline bool updiRead()
{
    // RX transistor inverts target logic.
    return !(PIND & _BV(PD5));
}

The rest of the program should never need to know that the transistors invert the signals.

It should think only in terms of:

drive low
release
read line

51. Why the New Interface Supports Lower Target Voltages Better

Suppose the target is powered from:

1.8 V

A bare 5 V Nano output could exceed the target’s permitted input level.

Our TX circuit does not have that problem.

The Nano only controls whether QTX is:

ON

or:

OFF.

When QTX is OFF, the target-side UPDI HIGH level is determined by the target itself.

Thus:

VTARGET = 1.8 V
→ UPDI HIGH ≈ target's 1.8 V domain

VTARGET = 3.3 V
→ UPDI HIGH ≈ target's 3.3 V domain

VTARGET = 5.0 V
→ UPDI HIGH ≈ target's 5 V domain

The Nano never imposes its own +5 V HIGH directly on the target.


52. Why the RX Interface Can Work at 1.8 V

Consider:

VTARGET ≈ 1.8 V
RBASE   = 47 kΩ

and approximate a BJT base-emitter drop around:

0.7 V

Then receiver base current is approximately:

[ I_B=\frac{1.8-0.7}{47k} ]

or about:

[ 23\ \mu A ]

The Nano RX collector pull-up is also:

47 kΩ

so QRX only needs to sink approximately:

[ \frac{5}{47k}\approx106\ \mu A ]

That requires an effective transistor current gain of less than:

5

to establish a strong LOW.

That is intentionally conservative.

The circuit therefore has good DC margin at low target voltage.

Dynamic timing still must be measured.

That distinction matters.


53. The Optional Target-Side Pull-Up

The target normally provides the UPDI idle-HIGH behavior itself.

Our final schematic nevertheless includes an optional pull-up-assist branch:

VTARGET
   |
 Schottky diode
   |
 33 kΩ
 DNP
   |
TARGET UPDI

The resistor is:

DNP — Do Not Populate

by default.

It exists because the actual rise time depends on:

  • target internal pull-up strength;
  • wiring capacitance;
  • cable length;
  • transistor capacitance;
  • baud rate;
  • target VDD.

If hardware testing shows that the target’s internal pull-up is sufficient:

leave R4 unpopulated.

That is our preferred outcome.

If testing shows that additional pull-up assistance significantly improves the waveform:

populate R4.

The circuit gives us that option without requiring a PCB change.


53.1 Why There Is a Diode in the Optional Pull-Up Branch

An older shared-pin UPDI device may intentionally receive approximately:

12 V

on the UPDI conductor during HV activation.

Without isolation, an external target pull-up would create:

12 V UPDI
    |
 pull-up resistor
    |
 VTARGET rail

which could backfeed the target supply.

The series Schottky diode is oriented so the optional branch can provide normal pull-up assistance from VTARGET toward UPDI while substantially blocking reverse current from the HV-charged UPDI node back into VTARGET.

This is another reason the optional pull-up is not simply a resistor from VTARGET to UPDI.


54. 12 V Tolerance of the UPDI Front End

The revised transistor interface also improves protection during old-style 12 V UPDI activation.

When approximately 12 V is deliberately applied to TARGET UPDI:

TX transistor

The TX 2N3904 is OFF and sees the elevated voltage at its collector.

A properly selected 2N3904 has collector-emitter voltage capability comfortably above this operating point.

RX transistor

The 12 V reaches the receiver base only through:

47 kΩ

so approximate base current is:

[ I_B=\frac{12-0.7}{47k} ]

or about:

[ 0.24\ mA ]

The Nano’s RX GPIO remains on the transistor’s collector side in the 5 V domain.

Nano TX

The TX GPIO reaches only the base of the TX transistor through its own resistor.

It is not directly connected to target UPDI.

Thus the Nano does not directly see the target-side 12 V.

That is a substantial improvement over the original one-resistor interface.


55. Target Operating Voltage

Our primary target:

AVR16DU28-I/SP

operates over approximately:

1.8–5.5 V

as do our selected:

AVR32DU28-I/SP
AVR16DD28-I/SP

examples.

The finalized electrical interface is designed to accommodate target operation over this range.

But we must separate:

designed for

from:

qualified at.

We will experimentally verify operation at representative values:

1.8 V
3.3 V
5.0 V

and at several UPDI bit rates.

Until we have those results, the correct statement is:

The interface is designed for low-voltage target support.

not:

Guaranteed 1.8 V operation at every UPDI speed.

That guarantee belongs to the test results, not the schematic.


55.1 Datasheet First, Tutorial Second

Whenever this article gives a value that could damage hardware if wrong, the device data sheet remains authoritative.

That includes:

  • target VDD limits;
  • absolute maximum input voltages;
  • UPDI speed;
  • HV activation voltage;
  • RESET limits;
  • pulse width;
  • target current;
  • clock restrictions;
  • NVM programming rules.

The article explains the system.

It does not replace target documentation.


56. UPDI Enable Behavior

The UPDI input contains internal pull-up behavior when the interface is enabled.

A normal enable operation begins when the debugger/programmer asserts the UPDI line LOW for the required interval and then releases it.

A useful conceptual sequence is:

programmer pulls LOW
        ↓
target detects activation
        ↓
target initializes UPDI clock/interface
        ↓
target may temporarily hold line LOW
        ↓
target releases line
        ↓
UPDI becomes idle HIGH

Representative modern-device timing includes an initial LOW pulse greater than approximately:

200 ns

followed by a target initialization/release interval that can extend from hundreds of microseconds into several milliseconds, depending on implementation.

The exact values always belong to the target documentation.


57. A Practical Ordinary-UPDI Activation Sequence

Conceptually:

1. Ensure TX transistor is OFF.

2. Verify the target line is not stuck LOW.

3. Turn TX transistor ON long enough to create
   the required enable LOW pulse.

4. Turn TX transistor OFF.

5. Observe UPDI_RX.

6. Wait for the target to release the line HIGH.

7. Send 0x55 SYNCH.

8. Send the first instruction.

Notice something important.

Because RX is always active, our Nano can observe the target UPDI state without changing D6 to an input.

That is one of the practical advantages of the new architecture.


58. Enable Is Not BREAK

Both involve pulling target UPDI LOW.

Their purposes differ.

Enable

Conceptually:

Wake or activate the UPDI physical interface.

BREAK

Conceptually:

Reset the UPDI communications state machine.

Our firmware should therefore retain separate operations:

updiEnable();
updiBreak();

rather than treating every long LOW pulse as the same operation.


59. The Programmer Still Has Two Serial Links

Our Nano participates in two serial conversations.

The first is:

PC
 |
 | USB serial
 v
Arduino Nano

The second is:

Arduino Nano
 |
 | D6 TX / D5 RX internally
 v
UPDI translator
 |
 | one target conductor
 v
Target AVR

These are completely different interfaces.

The host link may use:

115200 baud
8N1

while UPDI initially uses:

62,500 baud
8E2
SYNCH
BREAK
ACK
half-duplex protocol semantics

Their baud rates are independent.


60. Why Not Use the Nano Hardware UART for UPDI?

The Nano’s ATmega328P has a conventional USART.

However, that USART is normally involved in:

PC ↔ USB serial bridge ↔ Nano

communication.

We want that hardware UART available for our diagnostic and host protocol.

UPDI itself also requires unusual control over:

  • BREAK timing;
  • synchronization;
  • half-duplex ownership;
  • inverted external TX/RX behavior;
  • timing experiments.

So the useful architecture becomes:

hardware UART:
    PC communications

D6 / PD6:
    UPDI_TX timing engine

D5 / PD5:
    UPDI_RX sampling

The target still sees only one physical UPDI line.


61. Final Nano Pin Assignment

Our working article pin assignment is now:

Nano pinFunction
D2Activity/status indication
D3HV-enable/status indication
D4TARGET_RESET_CTRL
D5UPDI_RX
D6UPDI_TX
D7HV_ENABLE
D8TARGET_POWER_ENABLE
A0VTARGET_SENSE
A1HV_SENSE
A4/A5Optional I²C expansion
D10–D13Optional SPI expansion

The critical change from the original article draft is:

OLD:
D6 = bidirectional UPDI

FINAL:
D6 = UPDI_TX
D5 = UPDI_RX

Externally, however:

UPDI remains one wire.

62. UPDI Uses 8E2

An ordinary UPDI DATA frame contains:

1 start bit
8 data bits
1 even parity bit
2 stop bits

or:

8E2

So:

START
D0 D1 D2 D3 D4 D5 D6 D7
PARITY
STOP
STOP

contains:

12 bit periods

At 62,500 baud:

1 bit = 16 µs

and a full frame takes:

192 µs

At 225 kbaud:

1 bit ≈ 4.444 µs

and a full 12-bit frame takes approximately:

53.3 µs

63. Even Parity

UPDI uses even parity.

A simple parity function is:

uint8_t evenParity(uint8_t value)
{
    uint8_t parity = 0;

    for (uint8_t i = 0; i < 8; ++i) {
        parity ^= (value >> i) & 1u;
    }

    return parity;
}

Examples:

0x00
ones = 0
parity = 0

0x01
ones = 1
parity = 1

0x03
ones = 2
parity = 0

0xFF
ones = 8
parity = 0

64. Direct Port Access with Separate TX and RX

Arduino digitalWrite() is convenient.

It is not ideal for a carefully timed software UART.

On the ATmega328P:

Arduino D6 = PD6 = UPDI_TX
Arduino D5 = PD5 = UPDI_RX

Define:

#define UPDI_TX_BIT _BV(PD6)
#define UPDI_RX_BIT _BV(PD5)

Configure TX as an output:

static inline void updiTxInit()
{
    DDRD |= UPDI_TX_BIT;

    // LOW keeps the external NPN transmitter OFF.
    PORTD &= ~UPDI_TX_BIT;
}

Drive target UPDI LOW:

static inline void updiDriveLow()
{
    // HIGH turns QTX on.
    PORTD |= UPDI_TX_BIT;
}

Release target UPDI:

static inline void updiRelease()
{
    // LOW turns QTX off.
    PORTD &= ~UPDI_TX_BIT;
}

Read target UPDI:

static inline bool updiRead()
{
    // External receiver transistor is inverting.
    return !(PIND & UPDI_RX_BIT);
}

This is much simpler than repeatedly switching one GPIO between input and output modes.


65. We No Longer Need GPIO Direction Turnaround

The original one-pin design required:

D6 output during transmit
        ↓
D6 input during receive

The final hardware does not.

D6 remains:

TX output

and D5 remains:

RX input.

The target bus still changes ownership.

But the Nano’s internal GPIO directions do not.

Therefore:

updiSendByte(command);

// TX transistor is released by send routine.

updiReceiveByte(response);

does not require a DDR change between operations.

That removes an entire class of potential direction-switching mistakes.


66. Guard Time Still Matters

Separating TX and RX does not eliminate half-duplex timing.

The target and programmer still share one physical conductor.

The programmer must:

finish its last stop bit
        ↓
release TX transistor
        ↓
allow target to acquire the line
        ↓
sample response

UPDI guard-time mechanisms help prevent both ends from attempting to own the line simultaneously.

During development:

do not optimize guard time.

Use:

  • moderate baud rate;
  • normal ACK handling;
  • generous timing;
  • clear instrumentation.

Then shorten delays only after the protocol is reliable.


67. Sending an UPDI Byte with the New Hardware

Because the transmitter is open collector:

logical target LOW  = Nano TX HIGH
logical target HIGH = Nano TX LOW / released

A teaching implementation might resemble:

void updiSendByte(uint8_t value)
{
    noInterrupts();

    uint8_t parity = 0;

    // Start bit: target line LOW.
    updiDriveLow();
    delayBit();

    // Eight data bits, LSB first.
    for (uint8_t i = 0; i < 8; ++i) {
        bool bit = value & 0x01;

        if (bit)
            updiRelease();   // target logical HIGH
        else
            updiDriveLow();  // target logical LOW

        parity ^= bit;

        delayBit();

        value >>= 1;
    }

    // Even parity.
    if (parity)
        updiRelease();
    else
        updiDriveLow();

    delayBit();

    // Two stop bits = released/HIGH.
    updiRelease();
    delayBit();
    delayBit();

    interrupts();
}

The important conceptual change is:

we do not drive target HIGH.

We release the transistor and allow the target-side UPDI node to rise.


68. Why Interrupts Matter

Suppose our intended bit periods are:

16 µs
16 µs
16 µs
16 µs

but a timer interrupt occurs halfway through:

16 µs
16 µs
31 µs
16 µs

We no longer have a valid asynchronous frame.

Timing-critical UPDI bit transactions should therefore execute without unrelated interrupt jitter.

But interrupts should only be disabled for the short timing-critical region.

Good:

disable
send frame
enable

process

disable
receive frame
enable

Bad:

disable
erase chip
program Flash
verify entire device
enable

69. Timer1 Gives Us Better Timing

At:

F_CPU = 16 MHz
Timer1 prescaler = 8

Timer1 runs at:

2 MHz

so:

1 timer tick = 0.5 µs

At 62.5 kbaud:

bit period = 16 µs

which is:

32 Timer1 ticks.

That is extremely convenient.

static inline void waitTicks(uint16_t ticks)
{
    uint16_t start = TCNT1;

    while ((uint16_t)(TCNT1 - start) < ticks) {
    }
}

Then:

constexpr uint16_t UPDI_BIT_TICKS = 32;

gives us deterministic timing.


70. Receiving Is Harder

When transmitting, we determine when each bit begins.

When receiving, the target decides.

The receiver must:

wait for falling START edge
        ↓
move toward center of D0
        ↓
sample D0
        ↓
sample D1
        ↓
...
        ↓
sample D7
        ↓
sample parity
        ↓
verify STOP
        ↓
verify STOP

At 62.5 kbaud:

bit time = 16 µs

so the center of D0 occurs roughly:

1.5 bit periods

after the initial falling edge:

[ 1.5\times16\ \mu s=24\ \mu s ]

The external RX transistor already presents us with a safe Nano-level input.

updiRead() reverses the inversion so the higher-level code sees the actual target logic state.


71. Conceptual Receiver

bool updiReceiveByte(uint8_t &value)
{
    // TX must already be released.
    updiRelease();

    if (!waitForLow())
        return false;

    noInterrupts();

    // Move to center of D0.
    delayMicroseconds(24);

    uint8_t data = 0;
    uint8_t parity = 0;

    for (uint8_t i = 0; i < 8; ++i) {
        bool bit = updiRead();

        if (bit)
            data |= (1u << i);

        parity ^= bit;

        delayMicroseconds(16);
    }

    bool receivedParity = updiRead();

    delayMicroseconds(16);

    bool stop1 = updiRead();

    delayMicroseconds(16);

    bool stop2 = updiRead();

    interrupts();

    if (receivedParity != parity)
        return false;

    if (!stop1 || !stop2)
        return false;

    value = data;

    return true;
}

Again, this code explains the architecture.

The production implementation should use deterministic timer-based sampling.


72. Never Wait Forever

Embedded communications code must assume that the target can disappear.

This is dangerous:

while (updiRead()) {
}

Possible causes of an infinite wait include:

  • target not connected;
  • target unpowered;
  • wrong target pin;
  • broken wire;
  • UPDI unavailable;
  • failed translator;
  • damaged target.

Instead:

bool waitForLow(uint32_t timeoutUs)
{
    uint32_t start = micros();

    while (updiRead()) {
        if ((uint32_t)(micros() - start) >= timeoutUs)
            return false;
    }

    return true;
}

and similarly:

bool waitForHigh(uint32_t timeoutUs);

Every externally dependent operation should have a timeout.


73. Error Reporting

Instead of returning only:

true
false

use meaningful diagnostic states.

enum class UpdiError : uint8_t
{
    None,
    Timeout,
    StartBit,
    Parity,
    StopBit,
    BadAck,
    LineStuckLow,
    LineStuckHigh,
    NotInitialized,
    Protocol,
    UnsupportedDevice,
    VoltageOutOfRange
};

Then:

UPDI ERROR: parity failure

means something.

Compare that with:

ERROR

which tells us very little.


74. Our First Oscilloscope Test

Do not connect the target yet.

Have the Nano repeatedly transmit:

0x55

and observe two points if possible:

Nano D6 / UPDI_TX

and:

TARGET UPDI node

They should be inverted relative to one another because QTX is an NPN pull-down.

At:

62,500 baud

verify:

  • 16 µs bit period;
  • 8 data bits;
  • LSB first;
  • even parity;
  • 2 stop bits.

A simple loop:

void loop()
{
    updiSendByte(0x55);
    delay(10);
}

The 0x55 alternating pattern is especially easy to recognize.


75. Test More Than 0x55

Also transmit:

0x00
0xFF
0x01
0x80
0x55
0xAA

These reveal different bugs.

For example:

0x01

versus:

0x80

quickly exposes accidental MSB-first transmission.

Also probe both:

D6

and:

UPDI_DATA

so inversion is not mistaken for a firmware error.


76. Loopback and Echo Testing

The new hardware gives us a useful test that the original one-pin implementation did not provide as conveniently.

Because RX remains active while TX operates, the Nano can observe the UPDI line while transmitting.

When TX commands a target LOW:

D6 HIGH
→ QTX ON
→ target UPDI LOW
→ QRX OFF
→ D5 HIGH

When TX releases the line:

D6 LOW
→ QTX OFF
→ target UPDI HIGH
→ QRX ON
→ D5 LOW

Firmware can therefore compare:

expected target state

with:

observed RX state.

This can help detect:

  • stuck lines;
  • incorrect wiring;
  • target contention;
  • missing pull-up;
  • transistor faults.

It is not a replacement for an oscilloscope, but it is a useful diagnostic feature.


77. BREAK

BREAK is a sufficiently long LOW condition.

With our hardware:

BREAK = keep QTX ON.

For recovery from an unknown communication state, we intentionally use a long BREAK.

Representative traditional UPDI recovery durations are approximately:

4 MHz UPDI clock  → about 24.6 ms
8 MHz             → about 12.3 ms
16 MHz            → about 6.15 ms

A deliberately conservative development implementation is therefore:

void updiBreak()
{
    updiDriveLow();
    delay(25);
    updiRelease();
}

The important difference from the old direct-GPIO circuit is purely electrical:

updiDriveLow()

now means:

turn TX transistor ON.

78. Why Double BREAK?

Suppose the target is halfway through an operation.

The first abnormal BREAK may terminate the current operation.

The second arrives after the interface is receptive to a proper communications reset.

Thus:

BREAK 1:
    terminate confused/current state

BREAK 2:
    reset the now-receptive protocol engine

is generally more robust than a single attempt when the existing state is unknown.


79. SYNCH

Every new UPDI instruction begins with:

0x55

Define:

constexpr uint8_t UPDI_SYNCH = 0x55;

and give the action a name:

void updiSynch()
{
    updiSendByte(UPDI_SYNCH);
}

Then:

updiSynch();
updiSendByte(instruction);

is much more understandable than scattering unexplained 0x55 values throughout the program.


80. The Instruction Families

The principal instruction groups are:

000   LDS
001   LD
010   STS
011   ST
100   LDCS
101   REPEAT
110   STCS
111   KEY

Convenient broad opcode bases are:

constexpr uint8_t UPDI_LDS    = 0x00;
constexpr uint8_t UPDI_LD     = 0x20;
constexpr uint8_t UPDI_STS    = 0x40;
constexpr uint8_t UPDI_ST     = 0x60;
constexpr uint8_t UPDI_LDCS   = 0x80;
constexpr uint8_t UPDI_REPEAT = 0xA0;
constexpr uint8_t UPDI_STCS   = 0xC0;
constexpr uint8_t UPDI_KEY    = 0xE0;

The remaining bits encode instruction-specific details.

Do not treat values such as:

0x80
0x82
0xA0
0xC3

as magic.

Decode them.

Name them.

Document them.


81. LDCS — Our First Real UPDI Instruction

LDCS reads UPDI’s internal control/status register space.

This makes it an excellent first genuine command because it does not require us to understand:

  • target SRAM;
  • Flash;
  • NVMCTRL;
  • EEPROM;
  • fuses.

A simplified constructor:

uint8_t makeLdcs(uint8_t address)
{
    return UPDI_LDCS | (address & 0x0F);
}

Then:

SYNCH
LDCS
response

gives us a real request/response transaction.


82. Why LDCS Is Such a Valuable Milestone

If repeated LDCS reads work, we have already verified:

  • Nano TX GPIO;
  • TX transistor;
  • target UPDI node;
  • target power;
  • RX transistor;
  • Nano RX GPIO;
  • idle-state behavior;
  • software transmit timing;
  • start bit;
  • LSB-first order;
  • even parity;
  • two stop bits;
  • SYNCH;
  • instruction encoding;
  • turnaround;
  • target transmission;
  • receive timing;
  • parity checking;
  • stop-bit checking.

That is why the first serious test should be small and nondestructive.


83. STCS

STCS writes a byte into UPDI’s control/status space.

Conceptually:

void updiStcs(uint8_t address, uint8_t value)
{
    updiSynch();

    updiSendByte(
        UPDI_STCS | (address & 0x0F)
    );

    updiSendByte(value);
}

Not every UPDI write uses identical response semantics.

Do not assume:

every write → ACK

without checking the definition of the specific instruction.


84. ACK

For operations requiring acknowledgement, the conventional ACK byte is:

0x40

Define:

constexpr uint8_t UPDI_ACK = 0x40;

Then:

UpdiError expectAck()
{
    uint8_t value;

    UpdiError err = updiReceiveByte(value);

    if (err != UpdiError::None)
        return err;

    if (value != UPDI_ACK)
        return UpdiError::BadAck;

    return UpdiError::None;
}

The important point is:

Some byte arrived

does not mean:

The operation succeeded.


85. LDS and STS

Once UPDI internal-register access works reliably, we enter the target’s memory-mapped system space.

LDS means:

Load Direct from address

or approximately:

value = memory[address]

STS means:

Store Direct to address

or approximately:

memory[address] = value

This is where UPDI becomes enormously powerful.

Because the AVR system is heavily memory mapped, UPDI can reach:

  • SRAM;
  • peripheral registers;
  • NVMCTRL;
  • device information;
  • debugging resources.

86. LD and ST

LD and ST use UPDI’s internal pointer.

Instead of repeatedly sending:

address
data
address
data
address
data

we can establish a pointer and perform:

LD PTR++

or:

ST PTR++

for sequential transfers.

This becomes critical when reading or programming pages.


87. REPEAT

REPEAT loads the UPDI repeat counter.

An important detail is that a repeat value:

n

causes the following operation to occur approximately:

n + 1

times.

So for:

64 transfers

the repeat field is:

63

not 64.

That is exactly the kind of off-by-one rule that deserves an automated unit test.


88. KEY

The KEY instruction provides access to protected UPDI functionality.

Among the important 64-bit signatures are keys associated with:

  • NVM programming;
  • chip erase;
  • USERROW operations.

Representative keys include:

CHIP ERASE
0x4E564D4572617365

NVMPROG
0x4E564D50726F6720

These are not passwords or encryption keys.

They are protocol activation signatures.

Part III will use them in the actual NVM programming procedure.


89. Device-Specific “Magic Values” Must Be Identified

From this point onward every unexplained numeric constant should make us suspicious.

There are two broad classes.

Protocol constants

Examples:

SYNCH = 0x55
ACK   = 0x40
KEY opcode family = 0xE0

These belong to UPDI itself.

Device-dependent values

Examples:

  • Flash size;
  • Flash page size;
  • NVM controller generation;
  • memory-map addresses;
  • maximum UPDI speed;
  • signature bytes;
  • fuse layout;
  • HV activation voltage;
  • HV destination pin.

These belong to the target.

Our implementation will explicitly separate the two.


90. Device-Dependent Timing

A useful baseline for modern devices is:

ordinary enable LOW pulse:
    > about 200 ns

target initialization/release:
    hundreds of microseconds to milliseconds

safe initial baud:
    ≤ 225 kbaud for appropriate implementations

Higher UPDI rates may become available with:

  • higher target VDD;
  • different UPDI clock selection;
  • appropriate device support.

Our article still begins at:

62.5 kbaud

because understanding comes first.


91. HV Values for Our Reference Devices

This is where our chosen targets differ dramatically from the old shorthand:

“UPDI HV means 12 V.”

For our modern AVR DU/DD examples using separate RESET-based HV activation, representative documented behavior is approximately:

HV destination:
    RESET

VHV minimum:
    VDD + 2 V

VHV typical:
    7.5 V

VHV maximum:
    8.5 V

minimum pulse:
    about 10 µs

valid-key window:
    about 65 ms

subject always to the exact target’s documentation.

Our final hardware therefore supports a separate HV output path rather than assuming high voltage always belongs on RESET or always belongs on UPDI.

That distinction lets the same experimental programmer explore:

modern RESET-HV activation

and:

older 12 V shared-UPDI activation

without electrically treating them as the same thing.


92. VTARGET Sensing

The finalized hardware measures target voltage on:

A0 = VTARGET_SENSE

using a protected divider.

Our design uses approximately:

22 kΩ top
10 kΩ bottom

so:

[ V_{ADC}=V_{TARGET}\frac{10}{22+10} ]

or:

[ V_{ADC}=0.3125V_{TARGET} ]

Examples:

VTARGETADC voltage
1.8 V0.563 V
3.3 V1.031 V
5.0 V1.563 V
5.5 V1.719 V
12 V3.75 V
15 V4.69 V

The relatively large range is intentional.

VTARGET sense is not merely a precision measurement of normal 1.8–5.5 V operation.

It is also a protection/diagnostic channel.

A series resistor and Schottky clamp protect the ADC input, and a small capacitor provides filtering.


93. HV Sensing

The external HV rail is measured independently on:

A1 = HV_SENSE

using approximately:

33 kΩ top
10 kΩ bottom

giving:

[ V_{ADC}=V_{HV}\frac{10}{43} ]

or approximately:

[ V_{ADC}=0.2326V_{HV} ]

Representative values are:

HVADC
7.5 V1.74 V
8.5 V1.98 V
12 V2.79 V
18 V4.19 V
20 V4.65 V
~21.5 V~5.0 V

This gives us comfortable headroom around our intended:

7–8.5 V

and:

12 V

activation classes.

The programmer can therefore verify that the bench supply is reasonably set before enabling HV.


94. Target Reset Control

We also dedicate:

D4 = TARGET_RESET_CTRL

to a small NPN transistor that can pull target RESET LOW.

This allows ordinary reset control independently of the HV system.

Conceptually:

Nano D4
   |
 base resistor
   |
  NPN
   |
 target RESET

The transistor defaults OFF while the Nano resets.

This matters because:

ordinary reset

and:

high-voltage activation

are not the same operation even when a particular target happens to use the RESET pin for both.


95. Target Power Control

We reserve:

D8 = TARGET_POWER_ENABLE

for optional target-power switching.

The article reference schematic includes an optional relay driver.

The initial programming experiments do not require it to be populated.

But it becomes useful later for:

  • target power cycling;
  • repeatable startup tests;
  • HV recovery of a pin otherwise driven by application firmware;
  • automated test fixtures.

That capability is reserved now so that later firmware does not require a pin reassignment.


96. A Human-Readable Diagnostic Console

Before attempting AVRDUDE compatibility, build a simple command console.

For example:

NanoUPDI> help

Commands:

  wake
  break
  status
  sib
  signature
  vtarget
  vhv
  peek <address>
  poke <address> <value>
  reset
  info

Initially omit:

erase
writeflash
writefuse

Read first.

Write later.


97. Arduino IDE Serial Monitor

The simplest host console is already available through Arduino IDE.

void setup()
{
    Serial.begin(115200);

    updiTxInit();

    Serial.println();
    Serial.println(F("NanoUPDI experimental console"));
    Serial.println(F("READY"));
}

The Nano’s hardware UART remains dedicated to the host connection while D5/D6 perform UPDI.


98. Other Serial-Terminal Options

Arduino IDE is convenient but not mandatory.

Useful alternatives include:

  • PuTTY;
  • Tera Term;
  • CoolTerm;
  • minicom;
  • picocom;
  • screen;
  • GtkTerm.

On Linux:

picocom -b 115200 /dev/ttyUSB0

may be convenient.

The particular terminal is not important.

The observable command interface is.


99. Python and PySerial

Python lets us move from:

“Try it a few times.”

to:

“Perform 10,000 trials and count exactly what fails.”

For example:

import serial

port = serial.Serial(
    "/dev/ttyUSB0",
    115200,
    timeout=1
)

port.write(b"status\n")

print(
    port.readline().decode(errors="replace")
)

Then automate:

10,000 status reads
10,000 memory reads
multiple target voltages
multiple baud rates

and collect:

successes
timeouts
parity errors
framing errors
bad ACKs

That is engineering evidence.


100. The Nano’s Limitations Are Not Dead Ends

The classic Nano lacks:

  • native USB;
  • a specialized modern single-wire USART;
  • multi-voltage GPIO;
  • onboard HV generation;
  • programmable target power;
  • native target-voltage measurement.

But these are limitations of the bare Nano board.

Our final reference hardware adds:

two NPN transistors for UPDI translation
target reset transistor
HV switching transistors
VTARGET divider
HV divider
ADC protection
optional target-power relay

The Nano remains useful because inexpensive external circuitry supplies the capabilities its MCU lacks.

This is a broader embedded-system lesson:

A microcontroller’s integrated peripherals do not define the limits of the complete system.


101. Device Support Should Be Data Driven

Avoid:

if (device == AVR16DU28) {
    ...
}
else if (device == AVR32DU28) {
    ...
}
else if (device == AVR16DD28) {
    ...
}

scattered throughout the firmware.

Instead use something like:

struct DeviceDescriptor
{
    uint32_t signature;

    uint32_t flashSize;
    uint16_t flashPageSize;
    uint16_t eepromSize;

    uint8_t nvmVersion;
    uint8_t debugVersion;

    uint32_t minVdd_mV;
    uint32_t maxVdd_mV;

    uint32_t maxUpdiBaud;

    bool hvSupported;
    uint16_t hvTypical_mV;
};

Then:

signature
   ↓
device descriptor
   ↓
memory geometry
NVM generation
voltage limits
UPDI limits
HV behavior

all come from one auditable source.


102. Keep the Physical Layer Separate

The implementation should have clear boundaries.

updi_phy.cpp

    UPDI_TX control
    UPDI_RX sampling
    transistor inversion
    raw line state
    bit timing
    8E2 send
    8E2 receive
    BREAK
    enable/wake
updi_link.cpp

    SYNCH
    ACK
    retries
    recovery
    guard timing
updi_instruction.cpp

    LDS
    STS
    LD
    ST
    LDCS
    STCS
    REPEAT
    KEY
voltage_sense.cpp

    VTARGET
    HV
    calibration
    range validation
updi_device.cpp

    signatures
    device descriptors
updi_nvm.cpp

    NVM programming algorithms
hv_control.cpp

    switching
    route policy
    interlocks
    timing

Nothing in:

updi_phy.cpp

should need to know what Flash memory is.

Likewise:

updi_nvm.cpp

should never directly manipulate PORTD.


103. A First Serious Target Test

Once the transmitter and receiver have passed their oscilloscope tests:

release UPDI
      ↓
verify HIGH
      ↓
ordinary UPDI activation
      ↓
wait for target release
      ↓
send SYNCH
      ↓
issue harmless LDCS
      ↓
receive byte
      ↓
verify parity
      ↓
verify stop bits
      ↓
print result

Repeat it.

Then repeat it again.

Do not proceed merely because:

it worked once.

104. A Protocol Trace Mode

During development we want output such as:

[TX] 55
[TX] 80
[RX] 30

or:

[UPDI] SYNCH
[UPDI] LDCS 0
[UPDI] RX 0x30

A compile-time switch can enable it:

#define UPDI_TRACE 1

But never print inside a timing-sensitive bit loop.

Record the event.

Print it afterward.


105. Testing Firmware Is an Embedded-Development Instrument

The diagnostic firmware is not disposable scaffolding.

It represents a valuable engineering habit:

Turn assumptions into measurements.

Suppose we believe:

the target releases UPDI after X µs

or:

the 47 kΩ RX network works at 1.8 V

or:

the optional pull-up is unnecessary at 225 kbaud.

Do not merely reason about it indefinitely.

Write an experiment.

Measure it.

Examples:

Test 1

Does D6 produce exactly the expected bit timing?

Test 2

Does target UPDI have clean rise/fall edges after the TX transistor?

Test 3

Does D5 decode the target correctly at 1.8 V?

Test 4

How long does the target hold UPDI LOW during enable?

Test 5

Does LDCS work 10,000 times?

Test 6

Does it still work with R4 DNP at 225 kbaud?

Test 7

Does populating the optional 33 kΩ pull-up improve edge timing?

This is real embedded engineering.


106. Baud-Rate and Voltage Qualification

The new translator means our test matrix should include both:

baud

and:

VTARGET.

For example:

VTARGET62.5k115.2k125k225k
1.8 Vtesttesttesttest
3.3 Vtesttesttesttest
5.0 Vtesttesttesttest

At each combination:

10,000 status reads
10,000 memory reads
count failures

Also record:

  • optional pull-up populated or DNP;
  • target model;
  • cable length;
  • board/breadboard construction;
  • temperature if relevant.

This will tell us exactly what the electrical interface can honestly claim.


107. Target Voltage Can Affect Maximum UPDI Speed

Maximum recommended UPDI speed can depend on:

  • target voltage;
  • temperature;
  • target generation;
  • UPDI clock.

Representative newer-device values may step through approximately:

225 kbaud
450 kbaud
0.9 Mbaud
1.8 Mbaud

as operating conditions and UPDI clock permit.

The ATmega328P Nano itself will also become a practical limiting factor.

Our first AVR16DU28 experiment therefore remains:

62.5 kbaud

even at 5 V.

Correctness first.


108. Moving from LDCS to LDS

Once internal UPDI register reads are completely reliable, begin accessing ordinary target memory.

Conceptually:

LDS
 |
 +---- system address
           |
           +---- registers
           +---- SRAM
           +---- identification data
           +---- NVM-related regions

A useful high-level API might eventually be:

UpdiError updiLoadDirect(
    uint32_t address,
    uint8_t *destination,
    size_t count
);

The low-level code chooses the correct encoding.


109. Byte Order — Endianness

Whenever a multi-byte number crosses a protocol boundary, ask:

In what byte order?

Suppose:

0x1234

must be sent least-significant byte first.

The wire order becomes:

0x34
0x12

not:

0x12
0x34.

The same idea applies to:

  • memory storage;
  • addresses;
  • NVM values;
  • debug state;
  • host protocols.

A simple test:

void testAddressEncoding()
{
    uint8_t bytes[2];

    encodeAddress16(0x1234, bytes);

    assert(bytes[0] == 0x34);
    assert(bytes[1] == 0x12);
}

can eliminate an entire category of difficult bugs.


110. STS — Write Something Harmless First

After reading target memory successfully, we can implement writes.

But the first write should not be:

erase Flash

and certainly not:

modify fuse.

Instead use SRAM where appropriate:

read SRAM byte
save original
write test value
read back
verify
restore original
verify again

This tests:

  • STS encoding;
  • ACK handling;
  • target write path;
  • subsequent LDS;

without modifying nonvolatile memory.


111. Pointer-Based LD/ST Comes Next

Once direct access works:

LDS
STS

move to:

LD
ST

using the UPDI internal pointer.

Then:

LD PTR++
ST PTR++

Finally:

REPEAT + pointer post-increment.

This staged progression makes failures easier to isolate.


112. Then REPEAT

A good development sequence is:

single LDS
      ↓
single LD
      ↓
LD PTR++
      ↓
REPEAT + LD PTR++
      ↓
block read

If:

single LD works

but:

REPEAT block fails

then the basic physical and instruction layers are already largely vindicated.

The failure is much easier to localize.


113. Then Read the SIB

The System Information Block provides implementation information relevant to the target’s programming/debug architecture.

During development preserve both:

raw SIB bytes

and:

parsed interpretation.

Do not throw the raw information away merely because our parser believes it understands it.

New device families have a habit of making old assumptions visible.


114. Intermediate Firmware API

By this stage we want something closer to:

// Physical layer

void updiTxInit();

void updiDriveLow();
void updiRelease();

bool updiRead();

UpdiError updiEnable();

void updiBreak();
void updiDoubleBreak();

UpdiError updiSendByte(uint8_t value);
UpdiError updiReceiveByte(uint8_t &value);


// Link layer

void updiSynch();
UpdiError updiExpectAck();


// Instruction layer

UpdiError updiLdcs(
    uint8_t reg,
    uint8_t &value
);

UpdiError updiStcs(
    uint8_t reg,
    uint8_t value
);

UpdiError updiLds(...);
UpdiError updiSts(...);

UpdiError updiLd(...);
UpdiError updiSt(...);

UpdiError updiRepeat(
    uint8_t count
);

UpdiError updiSendKey(...);

UpdiError updiReadSib(...);


// Hardware support

uint16_t readVtargetMillivolts();
uint16_t readHvMillivolts();

void targetResetAssert();
void targetResetRelease();

void targetPowerEnable(bool enabled);

void hvEnable(bool enabled);

Still absent:

chip erase
Flash write
fuse write.

Those belong to the NVM layer and come later.


115. The Architectural Boundary

At this point:

+-------------------------------------+
| UPDI instructions                   |
| LDS STS LD ST LDCS STCS REPEAT KEY  |
+-------------------------------------+
                  |
+-------------------------------------+
| UPDI link                           |
| SYNCH ACK BREAK recovery            |
+-------------------------------------+
                  |
+-------------------------------------+
| Physical layer                      |
| D6 TX + D5 RX + transistor bridge   |
| target-side one-wire 8E2            |
+-------------------------------------+
                  |
                  v
========== TARGET SYSTEM BUS ==========
                  |
                  v
+-------------------------------------+
| NVM CONTROLLER                      |
|                                     |
| PART III                            |
+-------------------------------------+
                  |
                  v
        Flash / EEPROM / fuses

UPDI lets us communicate with the target.

The target’s NVM controller determines how we actually program it.


116. Corrected Complete Reference Wiring

The target-side interface should now be understood as:

                   ARDUINO NANO

D6 / PD6  UPDI_TX
     |
    47k
     |
   base
   QTX 2N3904
   emitter → GND
   collector
       |
       +-------------------------------- TARGET UPDI
       |
       +---- 47k ---- base QRX
                       QRX 2N3904
                       emitter → GND
                       collector
                           |
                           +------------ D5 / PD5 UPDI_RX
                           |
                          47k
                           |
                          +5V


OPTIONAL PULL-UP ASSIST:

VTARGET
   |
Schottky diode
   |
33k DNP
   |
TARGET UPDI

The target connector additionally provides:

TARGET_RESET
VTARGET
GND

The programmer is therefore not simply:

D6 → 4.7k → UPDI

anymore.

That simple circuit remains useful historically and pedagogically, but our final hardware has evolved beyond it.


116.1 Reference Connection Table

Programmer signalTarget connectionPurpose
TARGET_RESETRESETNormal reset / applicable HV-reset use
VTARGETVDDVoltage sense/reference
UPDI_DATAUPDIOne-wire UPDI
GNDGNDCommon reference

Internal Nano mapping:

NanoFunction
D4TARGET_RESET_CTRL
D5UPDI_RX
D6UPDI_TX
D7HV_ENABLE
D8TARGET_POWER_ENABLE
A0VTARGET_SENSE
A1HV_SENSE

116.2 Target Decoupling

Target decoupling remains separate from the UPDI circuitry.

Use at least:

VDD ----||---- GND
       100 nF

close to the AVR power pins.

On a breadboard, an additional:

1–10 µF

bulk capacitor is useful.

The capacitors are target-power components.

They are not part of UPDI signaling.


117. Suggested Programming Header

A convenient development connector is:

1 RESET
2 VTARGET
3 GND
4 UPDI

Our schematic may use a different physical ordering depending upon connector layout, but the logical four-signal set is:

RESET
VDD/VTARGET
GND
UPDI

For a simple non-HV experiment, only:

UPDI
VDD
GND

may be needed.


118. Nano Autoreset

Classic Nano boards commonly reset when the host opens the serial port because the USB-UART circuit manipulates DTR.

This is convenient during sketch upload.

It can be surprising during programmer use.

Instead of modifying the Nano immediately, our host protocol can expect:

host opens port
        ↓
Nano resets
        ↓
UPDI TX defaults safe/OFF
        ↓
HV defaults OFF
        ↓
Nano initializes
        ↓
Nano sends READY
        ↓
host proceeds

This makes safe hardware default states particularly important.


119. Startup Banner

A useful updated startup report might be:

NanoUPDI Experimental Programmer 0.3
-------------------------------------

Host baud:       115200
UPDI baud:        62500

UPDI TX:          D6 / PD6
UPDI RX:          D5 / PD5

Reset control:    D4
HV enable:        D7
Target power:     D8

VTARGET sense:    A0
HV sense:         A1

Target profile:   AVR16DU28-I/SP

Target VDD:       5.02 V
HV supply:        0.00 V

Optional UPDI
pull-up:          DNP

Trace mode:       enabled

READY

Now there is no ambiguity about which hardware version the firmware expects.


120. Keep Measurements with the Code

When testing a configuration, record:

firmware Git commit
target MCU
target VDD
UPDI baud
UPDI pull-up DNP/populated
wiring/cable length
test count
failure count

For example:

Firmware:      8c17a3e
Target:        AVR16DU28-I/SP
VDD:           3.302 V
UPDI baud:     125000
R4 assist:     DNP
Cable:         10 cm
Reads:         10000

Timeouts:      0
Parity errors: 0
Frame errors:  0
Bad ACK:       0

Now:

“It seems reliable.”

becomes:

“Ten thousand transactions completed with zero observed failures under a documented configuration.”


121. A Companion Nano UPDI Sniffer / Analyzer

We can go farther than merely observing UPDI_DATA on an oscilloscope.

For this series we can use a separate companion project:

Nano UPDI Sniffer / Analyzer

The intended project provides:

  • Nano firmware;
  • host software;
  • wiring documentation;
  • schematics;
  • PCB design;
  • protocol-decoding capabilities.

The analyzer is a separate instrument from the programmer.

The programmer says:

Perform this operation.

The analyzer says:

Tell me what actually happened.

That difference becomes extremely useful when our implementation disagrees with a known-good tool.


121.1 Why a Sniffer Is So Valuable

A protocol analyzer gives us a third perspective.

We already have:

source code

and:

electrical waveform.

The analyzer adds:

decoded protocol meaning.

Thus:

SOURCE CODE
    |
    v
"send LDCS"

WIRE
    |
    v
55 82

ANALYZER
    |
    +-- SYNCH
    +-- LDCS

TARGET
    |
    v
30

Theory, source, waveform, and target response can now be compared directly.


121.2 Sniffer Versus Programmer

When our programmer misbehaves, compare its traffic against:

  • PICkit;
  • Atmel-ICE;
  • another known-good programmer.

A differential capture may reveal:

  • missing BREAK;
  • extra BREAK;
  • different guard timing;
  • unexpected ACK handling;
  • different KEY ordering;
  • additional reset sequences;
  • different NVM commands.

That is far more productive than guessing.


121.3 A Higher-Level Decoder

The analyzer can evolve through layers:

Layer 1:
    raw transitions

Layer 2:
    8E2 bytes

Layer 3:
    SYNCH / ACK / BREAK

Layer 4:
    LDS / STS / LD / ST
    LDCS / STCS
    REPEAT / KEY

Layer 5:
    register names

Layer 6:
    NVM operations

Layer 7:
    semantic summaries

Eventually:

KEY NVMPROG

RESET ASSERT
RESET RELEASE

PROGRAMMING MODE ENTERED

SET PTR = 0x008000

REPEAT 63

WRITE 64 BYTES

NVM WRITE

WAIT BUSY

READ BACK

VERIFY OK

becomes possible.


122. Test Firmware as a Scientific Method

The combination of:

  • small Arduino test programs;
  • Serial Monitor;
  • terminal emulator;
  • Python/PySerial;
  • oscilloscope;
  • logic analyzer;
  • Nano UPDI Sniffer;

gives us an unusually capable hobbyist laboratory.

The methodology is:

form belief
     ↓
construct smallest useful test
     ↓
run repeatedly
     ↓
measure
     ↓
compare with documentation
     ↓
revise understanding

Not:

“I think the peripheral probably behaves this way.”

But:

“The data sheet says this. The test produced this. The scope shows this. Ten thousand trials produced zero exceptions.”

That is engineering.


123. Git and Tests

Even this Nano project deserves version control.

Begin:

git init

Then make meaningful commits:

Initialize Nano UPDI firmware

Add UPDI TX transistor control

Add inverted UPDI RX support

Add timer-based 8E2 transmitter

Add timer-based receiver

Add enable and double-BREAK recovery

Implement LDCS and STCS

Add VTARGET measurement

Add HV measurement

Add automated serial reliability tests

Add AVR16DU28 descriptor

Add analyzer integration notes

Deterministic functions should have independent tests:

  • parity;
  • instruction encoding;
  • address serialization;
  • repeat counts;
  • target lookup;
  • timeout arithmetic;
  • endian conversion;
  • voltage-scaling math.

Tests become increasingly important once Part III begins modifying nonvolatile memory.


124. Review of Part II

We now have a concrete target:

AVR16DU28-I/SP

Its:

  • 28-pin SPDIP package;
  • 1.8–5.5 V supply range;
  • modern AVR architecture;
  • UPDI support;

make it particularly suitable for a breadboard tutorial.

We also identified:

AVR32DU28-I/SP
AVR16DD28-I/SP

as useful alternatives.

More importantly, we have moved beyond the original:

D6 → 4.7k → UPDI

teaching circuit.

Our finalized programmer uses:

D6 = UPDI_TX
D5 = UPDI_RX

and two NPN transistors to combine those internal paths into:

one target UPDI conductor.

That gives us:

  • open-collector target drive;
  • target-referenced HIGH levels;
  • lower-VDD compatibility by design;
  • Nano-domain RX logic;
  • improved tolerance of old 12 V-on-UPDI activation;
  • permanent receive visibility;
  • easier TX/RX firmware.

We also added:

D4 = TARGET_RESET_CTRL
D7 = HV_ENABLE
D8 = TARGET_POWER_ENABLE

A0 = VTARGET_SENSE
A1 = HV_SENSE

and established a protected, optional/DNP target-side pull-up assist.

We established the development environment:

Arduino Nano
Arduino IDE
Serial Monitor
terminal emulator
Python + PySerial
oscilloscope
logic analyzer
Nano UPDI Sniffer

and treated all of them as legitimate embedded-development instruments.

We implemented the conceptual foundation for:

8E2 TX
8E2 RX
parity
timeouts
enable
BREAK
double BREAK
SYNCH
ACK
LDCS
STCS
LDS
STS
LD
ST
REPEAT
KEY

We introduced:

endianness

and recognized that byte ordering must be explicit whenever a multi-byte value crosses a protocol boundary.

Most importantly, we established the development methodology:

assumption
    ↓
small test
    ↓
measurement
    ↓
automated repetition
    ↓
evidence
    ↓
implementation

That methodology becomes even more important in the next part because we are about to cross the boundary between:

talking to an AVR

and:

modifying an AVR.


125. Where We Go Next

Part III will turn the Nano from a communications experiment into an actual AVR programmer.

We will begin with the AVR16DU28-I/SP as our primary target and explicitly note differences for the AVR32DU28-I/SP and AVR16DD28-I/SP where they matter.

We will cover:

Device identification

  • SIB;
  • signature;
  • revision;
  • descriptor lookup.

The AVR memory map

Including:

  • Flash;
  • SRAM;
  • EEPROM;
  • FUSE;
  • USERROW;
  • signature row;
  • peripherals;
  • NVMCTRL.

Entering NVM programming mode

We will trace:

UPDI established
       ↓
NVMPROG KEY
       ↓
required reset sequence
       ↓
read programming status
       ↓
confirm programming mode

NVM-controller generations

We will distinguish:

UPDI transport protocol

from:

target NVM programming protocol.

This is one of the central architectural lessons of the entire series.

Flash reading

Our first NVM operation will remain nondestructive.

Chip erase

Then:

erase request
status polling
completion
error handling

Flash programming

Including:

  • Flash geometry;
  • target-specific NVM commands;
  • pointer setup;
  • REPEAT;
  • erase/write procedure;
  • busy/error polling;
  • readback verification.

EEPROM

EEPROM gets its own programming procedure rather than being treated as miniature Flash.

Fuses

Fuse writes will be classified according to risk.

Dangerous configuration changes require explicit safeguards.

USERROW

We will examine:

  • where it lives;
  • whether chip erase affects it;
  • its programming requirements.

Intel HEX

We will decode records such as:

:10010000214601360121470136007EFE09D2190140

and show how host software converts a sparse image into programmer operations.

Host software

We will progress from:

Arduino Serial Monitor

to:

Python + PySerial

and build a host programmer capable of:

identify
read
backup
erase
program
verify

Verification

Every destructive operation will be followed by readback verification.

Analyzer comparison

Finally, we will capture our programming sequence and compare:

data-sheet procedure
        =
our source code
        =
wire traffic

Only after ordinary programming is reliable will Part IV enable the externally supplied HV recovery hardware, including the critical distinction between:

modern RESET-HV

and:

older approximately 12 V shared-UPDI activation.

IMPLEMENTING UPDI FROM SCRATCH

All You Ever Needed To Know About UPDI, And Then Some!

Leave a Reply

Your email address will not be published. Required fields are marked *