HV UDPI Instability

I have built a HV UPDI programmer based on work by Technob Tiny UPDI-HV Programmer.

For size and pin count issues, I want to use the UPDI pin for sending serial debug out through the UDPI pin (but re-tasked as GPIO).

This means I have one connection for both programming and seeing debug messages.

I can get it all to work but it’s a bit unstable.

I’ll go through the instability messages etc and finish up with more details about my UPDI programmer.

Before I get started, I’d just like to confirm that without the HV pulse complication, my programmer works great with targets that still have UPDI pin assigned.

My hunch is that my issue is to do with pulse timers but I’d welcome any advice.

OK - story time.
I check I can upload to device (ATtiny402) with programmer set in normal mode (no HV pulse) with UPDI pin still as UPDI.

I can also upload to same device with programmer set to include the HV pulse at start of programming (but no serial debug from UPDI pin - PA0 occurs as I have not changed the fuses yet).

I use Arduino 1.819 with megaTinyCore to ‘burn bootloader’ and set fuse values to set UPDI pin as GPIO pin.

I can upload using programmer with HV set and can (sometimes) successfully upload (and serial debug appears on UPDI pin - PA0).

A digression here. You need to power cycle the device after burning bootloader to change PA0 from UPDI mode - set by HV pulse - back to GPIO mode. Hence my programmer looks for a rise in DTR and after a while uses this to give the target device a power reset.

Back to the story.

Occasionally I get an upload error:

Processing ATtiny402 (platform: atmelmegaavr; board: ATtiny402; framework: arduino)
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/atmelmegaavr/ATtiny402.html
PLATFORM: Atmel megaAVR (1.6.0) > ATtiny402        
HARDWARE: ATTINY402 16MHz, 256B RAM, 4KB Flash     
PACKAGES:
 - framework-arduino-megaavr-megatinycore @ 2.5.11 
 - tool-avrdude-megaavr @ 3.60300.220118 (6.3.0)   
 - toolchain-atmelavr @ 3.70300.220127 (7.3.0)     
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 16 compatible libraries
Scanning dependencies...
Dependency Graph
|-- SendOnlySoftwareSerial
Building in release mode  
Checking size .pio\build\ATtiny402\firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [==        ]  19.1% (used 49 bytes from 256 bytes)
Flash: [=======   ]  70.1% (used 2873 bytes from 4096 bytes)
Configuring upload protocol...
AVAILABLE: jtag2updi
CURRENT: upload_protocol = jtag2updi       
Looking for upload port...
Using manually specified: COM16
Uploading .pio\build\ATtiny402\firmware.hex
Connecting to SerialUPDI
pymcuprog.serialupdi.application - ERROR - SIB read returned invalid characters
pymcuprog.serialupdi.application - WARNING - Unable to read SIB from device; attempting double-break recovery...
Pinging device...
Ping response: 1E9227
Erasing device before writing from hex file...  
Writing from hex file...
Writing flash...
pymcuprog.pymcuprog_main - ERROR - Error with st
Done.
*** [upload] Error 1

On trying again I am told the device is in a locked state:

Processing ATtiny402 (platform: atmelmegaavr; board: ATtiny402; framework: arduino)
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/atmelmegaavr/ATtiny402.html
PLATFORM: Atmel megaAVR (1.6.0) > ATtiny402        
HARDWARE: ATTINY402 16MHz, 256B RAM, 4KB Flash     
PACKAGES:
 - framework-arduino-megaavr-megatinycore @ 2.5.11 
 - tool-avrdude-megaavr @ 3.60300.220118 (6.3.0)   
 - toolchain-atmelavr @ 3.70300.220127 (7.3.0)     
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 16 compatible libraries
Scanning dependencies...
Dependency Graph
|-- SendOnlySoftwareSerial
Building in release mode
Checking size .pio\build\ATtiny402\firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [==        ]  19.1% (used 49 bytes from 256 bytes)
Flash: [=======   ]  70.1% (used 2873 bytes from 4096 bytes)
Configuring upload protocol...
AVAILABLE: jtag2updi
CURRENT: upload_protocol = jtag2updi
Looking for upload port...
Using manually specified: COM16
Uploading .pio\build\ATtiny402\firmware.hex
Connecting to SerialUPDI
pymcuprog.serialupdi.application - ERROR - Key status = 0x00
The device is in a locked state and is not accessible; a chip erase is required.
Locked AVR UPDI devices can:
 - be unlocked using command: erase --chip-erase-locked-device
 - write user row values using command: write -m user_row --user-row-locked-device
*** [upload] Error 1

But I may need to enter the chip erase command multiple times before it works:

PS D:\Users\Peter\Documents\PlatformIO\Projects\ATtiny402_WalkaboutPowerMonitor> pymcuprog erase --chip-erase-locked-device -t uart -u COM16 -d Attiny402
Connecting to SerialUPDI
pymcuprog.serialupdi.application - ERROR - Key status = 0x00
Pinging device...
pymcuprog.nvm - ERROR - Unable to read signature for detected device in family: 'tinyAVR'
pymcuprog.pymcuprog_main - ERROR - Unable to read device ID
Done.

PS D:\Users\Peter\Documents\PlatformIO\Projects\ATtiny402_WalkaboutPowerMonitor> pymcuprog erase --chip-erase-locked-device -t uart -u COM16 -d Attiny402
Connecting to SerialUPDI
pymcuprog.pymcuprog - ERROR - Operation failed with PymcuprogError: UPDI initialisation failed

PS D:\Users\Peter\Documents\PlatformIO\Projects\ATtiny402_WalkaboutPowerMonitor> pymcuprog erase --chip-erase-locked-device -t uart -u COM16 -d Attiny402
Connecting to SerialUPDI
Pinging device...
Ping response: 1E9227
Chip/Bulk erase:
- Memory type eeprom is conditionally erased (depending upon EESAVE fuse setting)
- Memory type flash is always erased
- Memory type lockbits is always erased
Erased.
Done.

My question is where should I look to improve my set up to make this more stable?

I have tried a selection of devices so I do not believe it to be device specific.

I think it might be a timing thing - for example, how fast do I need to get the HV pulse ‘on’ at onset of programming and DTR drop from serial card - I am putting this through a generic pin debounce routine (albeit set to 0ms timed by millis() but than might be too slow.

I struggle with spec sheets but from the UPDI Enable with 12V Override of RESET Pin section it mentioned using a pulse of 100 μs to 1 ms I measured my pulse at 127us.

Here’s my circuit:

Here’s the code in the AVR that monitors DTR and controls the HV pulse.

// ATtiny402_HV_UPDI
// Peter Charles-Jones

// Code Spell Checker Visual Studio extension - provides spell checking for comments
// cSpell:includeRegExp CStyleComment

/* The Environment
This  was designed using PlatformIO (free) which is an extension of Visual Studio (free)
The big advantage to Arduino 1.8.19 (free) is the intellisense editor which can suggest register names, highlight scope errors, rename variables and so on 
Atmel's Studio 7 (free) is worth a mention as it provides a basic set of peripheral setup code via its AtmelSTART projects
It also has a really good GUI fuse editor for older style MCUs eg ATtiny85, Atmega328 etc
*/ 

/* What is the device
See the data sheet here <https://ww1.microchip.com/downloads/en/DeviceDoc/ATtiny202-402-AVR-MCU-with-Core-Independent-Peripherals_and-picoPower-40001969A.pdf>
Series '0' and '1' devices use Universal Programming Debug Interface (UPDI) which uses 1 pin interface plus ground and positive.
Typically you need to build your own UPDI programmer by modifying a standard serial interface module - see the links below.
If you want to use the UPDI pin as a standard GPIO pin, you need to follow the instructions at <http://www.technoblogy.com/show?48MP> 
to create a High Voltage (12v) programmer.
If you do this - suggest you use the UPDI pin as push button input or as debug serial output where either the programmer or serial monitor 
are connected (but not both at the same time).
*/

/* General approach to UPDI
	I purchased an original ATtiny402 direct from Microchip - the exact part code was ATTINY402-SSN. 
	This part was available as it's not automotive spec and comes in a tube so there was no part reel cutting fee.
	Beware buying devices from generic suppliers such as eBay, AliExpress - I purchased devices with duff device ID (and pin multiplex table issues)

	The upload command used pymcuprog which had to be installed within PlatformIO terminal break out rather than directly on PC.

	The ATtiny402 was programmed using a UPDI programmer made from a UTP to serial device with a diode between TXD and RXD.

	--------------------    	                          To Target device
		           	DTR|	                  330 ohm     __________________
		internal    Rx |--------------,--------\/\/------| UPDI------------->
	  Tx---/\/\/\---Tx |-------|<|---'          .--------| Gnd     
	   	resistor    Vcc|---------------------------------| Vcc
	    typ 1-2k    CTS|                     .`          |__________________
	       			Gnd|--------------------'             If you make a 3-pin connector, use this pinout
	--------------------

	Here's a bunch of links that describe this in more detail....
	<https://github.com/SpenceKonde/AVR-Guidance/blob/master/UPDI/jtag2updi.md>
	<https://pypi.org/project/pymcuprog/>
	<https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/PlatformIO.md>
	<https://docs.platformio.org/en/latest/platforms/atmelmegaavr.html#upload-using-pymcuprog-serialupdi>
	<https://community.platformio.org/t/problems-migrating-from-studio-7-to-platformio-attiny402-with-pymcuprog-custom-tool/29352/9>

	I used Studio START code generator to get used to the peripheral configuration code
	I put all the initial set up required in a system_init() routine and put this code at the bottom 
	below the main() loop structure.

	Direct Port Manipulation - Warning

	See what Spence Konde (AKA Dr Azzy) has to say about direct port manipulation 
	<https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/Ref_DirectPortManipulation.md>

	Interrupts and flag setting
	The setting of multiple FLAG bits is not atomic (may be interrupted by an interrupt service routine) and if an interrupt sets a bit during such an update eg 
	FLAG &= ~((1<<night_set_bp)|(1<<detection_set_bp));	
	the bit set by the interrupt would be lost - so we need a separate set of interrupt flags - hence INT_FLAG and this must be defined as volatile

*/
/* Pin Multiplexing summary
pin Pin Name  Other/Special ADC0 AC0  USART0  SPI0  TWI0  TCA0  TCB0  CCL
6   PA0       ~RESET/UPDI   AIN0      XDIR    ~SS                     LUT0-IN0
5   PA1                     AIN1      *TXD    MOSI  SDA   WO1         LUT0-IN1
4   PA2       EVOUT0        AIN2      *RxD    MISO  SCL   WO2         LUT0-IN2
7   PA3       EXTCLK        AIN3 OUT  XCK     SCK         WO0/WO3
8   GND
1   VDD
2   PA6                     AIN6 AINN0 TXD    *MOSI             WO    LUT0-OUT
3   PA7                     AIN7 AINP0 RXD    *MISO       *WO0        LUT1-OUT

Note * indicates alternate pin

TCA0 is the default timer used for millis() and Delay() in the Arduino wrapper.

When designing a thing, you will be constrained by what your MCU pin multiplex table can support. 
Best start with the basics to see if what you are trying to do will fit your MCU
*/
/* Pin information
			    ATtiny402
			     ---u---
    	  	VDD|	    |GND
   	 	PWR PA6|	    |PA3 HVT
  		TVC PA7|	    |PA0 
	  	CTR PA1|		|PA2 DTR
    			 -------
*/
/* isues 
 If you corrupt the lock bits (with UDPI pin as GPIO) and PlatformIO says you need to do a chip erase - you may just re-burn the boot loader from Arduino IDE 
 Or you could try the chip erase command from the terminal:

 pymcuprog erase --chip-erase-locked-device -t uart -u COM16 -d Attiny402

 Currently this aspect of the HV UPDI is a bit flakey and unstable - looking for improvements
*/

// put pre-compile code here:
#define F_CPU 16000000L		// this tells the code the clock is running at 16MHz (it does not define how fast the clock is running)- see CLKCTRL_init()

// the include code tells the compiler to use code in these libraries - generally this makes coding easier
#include <Arduino.h>        // so we can use the Arduino framework - thanks Dr Azzy!

// the define statements tell the compiler to replace the first element with the second 
// Define names for the pins for easy reference
#define PWR_bp 6 // PA6 output Power for boost 12v (so you get a built in 'POWER ON' LED on the module)
#define VCC_bp 7 // PA7 output VCC for target so you can control a target power reset
#define CTR_bp 1 // PA1 output to control switch to actually switch in teh HV pulse into the UPDI connector
#define HVT_bp 3 // PA3 input HV toggle button - starts off in normal mode (not HV) - an LED hung on VCC_bp pin indicates the power reset
#define DTR_bp 2 // PA2 input DTR detector - hoosk into DTR of serial to USB adapter

#define button_debounce 50				// button debounce (ms)
#define DTR_debounce 0					// signal debounce (ms)- don't  want to delay application of HV pulse when DRT drops and it won't need debouncing
#define HV_pusle_time_max 100			// HV pulse duration (us) [actually measured pulse at CTR / PA1 of 126us]
#define post_prog_delay_time_max 8000 	// delay after DRT raise before power reset to target AVR (ms) The timer is reset while FLAG: Prog_bp is set
#define reset_time_max 2000				// power down period for target AVR after post prog delay (ms)

// bit definitions for FLAG - an 8 bit byte, each bit is used to flag status info to the main code
#define HV_On_bp 0 						// FLAG :HV powered up
#define PROG_bp 1						// FLAG: UPDI programming in progress
#define HV_PULSE_bp 2					// FLAG: HV pulse is in progress
#define Pause_Pulse_bp 3				// FLAG: Post programming pause is in progress
#define Reset_Pulse_bp 4				// FLAG: Target AVR VCC has been set to GND


// variables used by setup() and loop() - these functions are specific to Arduino. 

// global variable declaration
uint8_t tempPINA = VPORTA_IN;                  							// used as snapshot for PINA status if it changes mid process
uint8_t pinAHistory = VPORTA_IN;                       					// the last read PINA status
uint8_t debounced_PINA = VPORTA_IN;										// debounced copy of the PINA register
uint8_t FLAG;															// flags for program control - see above bit definitions 

unsigned long last_HVT_time = millis();                					// debounce timer (ms)
unsigned long last_DTR_time = millis();                					// debounce timer (ms)
unsigned long HV_pulse_time = micros();									// HV pulse timer (us)
unsigned long post_prog_delay_time = millis();							// post programming delay timer (ms)- to give you time to open up the terminal before teh target AVR is power cycled
unsigned long reset_time = millis();									// target AVR power off pulse timer (ms)

// put function declarations here: - a 'place holder' allowing the complier to reference functions before they have been fully defined.
int pinChange (unsigned long* last_time, int pinID, int debounce);		// has pin changed?
int pinHIGH (int pinID);												// is pin HIGH?
void system_init();   													// initializes the peripherals 

void setup() {
  	// put your setup code here, to run once:
	system_init();														// initialize peripherals
}

void loop() {
	// put your main code here, to run repeatedly:
		
	// check the pins for any changes
	tempPINA = VPORTA_IN;                             					// get a snapshot of current PIN setting

	// Check HVT for a key press 
	if (pinChange(&last_HVT_time, HVT_bp, button_debounce)){
		if (!(pinHIGH(HVT_bp))){               							// HVT is LOW - CC - button pressed
			if(!(FLAG & (1<<HV_On_bp))){
				FLAG |= (1<<HV_On_bp);
				VPORTA_OUT |= (1<<PWR_bp);								// power up the 12v boost board
			}
			else {
				FLAG &= ~(1<<HV_On_bp);
				VPORTA_OUT &= ~(1<<PWR_bp);								// power down the 12v boost board
			}
    	}
	}

// Check DTR for a key press 
	if (pinChange(&last_DTR_time, DTR_bp, DTR_debounce)){
		if (!(pinHIGH(DTR_bp))){               							// DTR is LOW - CC - UPDI programming in progress
			if(!(FLAG & (1<<PROG_bp))){
				FLAG |= (1<<PROG_bp);									// flag that programming has started
				if (FLAG & (1<<HV_On_bp)){								// HV programming so send HV pulse
					HV_pulse_time = micros();							// start HV pulse
					VPORTA_OUT |= (1<<CTR_bp);							// switch the 12v onto UDPI output
					FLAG |= (1<<HV_PULSE_bp);							// set the flag
				}
    		}
		}
		else{															// DTR has gone HIGH - UDPI programming is over
			if (FLAG & (1<<PROG_bp)){									// programming was in progress
				post_prog_delay_time = millis();						// reset the delay timer - can happen multiple times if programmer can't establish UDPI link with target AVR
				FLAG |= (1<<Pause_Pulse_bp);	
			}
		}
	}
	pinAHistory = tempPINA; 											// update the PINA history with the latest snapshot of PINA

	if (FLAG & (1<<HV_PULSE_bp)){										// if an HV pulse is in progress
		if ((micros() - HV_pulse_time) >= HV_pusle_time_max){			// time to end the HV pulse (allowed value range 10ns to 4ms - we have 127us)
			FLAG &= ~(1<<HV_PULSE_bp);									// clear the flag
			VPORTA_OUT &= ~(1<<CTR_bp);									// switch 12 v out
		}	
	}		

	if (FLAG & (1<<Pause_Pulse_bp)){										// if an delay pulse is in progress
		if ((millis() - post_prog_delay_time) >= post_prog_delay_time_max){	// time to end 
			FLAG &= ~(1<<Pause_Pulse_bp);									// clear the flag
			reset_time = millis();											// reset reset timer
			FLAG |= (1<<Reset_Pulse_bp);									// set reset in progress flag
			VPORTA_OUT &= ~(1<<VCC_bp);										// drop power to target AVR
			FLAG &= ~(1<<PROG_bp);											// clear PROG flag at the end of the pause to allow timer to be restarted multiple times if DTR goes up and down
		}	
	}		

	if (FLAG & (1<<Reset_Pulse_bp)){										// if an delay pulse is in progress
		if ((millis() - reset_time) >= reset_time_max){						// time to end 
			FLAG &= ~(1<<Reset_Pulse_bp);									// clear the flag
			VPORTA_OUT |= (1<<VCC_bp);										// power restored to target AVR
		}	
	}
}

// put function definitions here:	

int pinChange (unsigned long* last_time, int pinID, int debounce){ 		// has the pin changed state?
	if ((tempPINA & (1<<pinID))!= (pinAHistory & (1<<pinID) )){			// last pin state different
    	*last_time = millis();
  	}
	if ((millis() - *last_time) > debounce){							// debounce time has elapsed
		if ((tempPINA & (1<<pinID))!=(debounced_PINA & (1<<pinID))){	// is the current pin state different from the current one
			debounced_PINA &= ~(1<<pinID);								// mask out the bit position
			debounced_PINA |= (tempPINA & (1<<pinID));					// get the bit value at bit location and OR into PINS
    		return 1;													// pin has changed
    	}
  	}
	return 0;                                                 			// pin has not changed state
}

int pinHIGH (int pinID){												// is the pin HIGH?
	if (debounced_PINA & (1<<pinID)){                                  	// pin value is 1.. switch / button is Open Circuit
    	return 1;                                             			// HIGH
  	}
	else{                                                     			// pin value is 0 .. switch / button is Closed Circuit to ground
    	return 0;                                             			//LOW
	}
}

// Interrupt Service Routines

/*	Helper routines - to make the code more readable */
void disablePortAPullup(uint8_t pin){											// code only does this to ADC pin and this has no interrupt so ignore the ISC code
	uint8_t *port_pin_ctrl = ((uint8_t *)&PORTA + 0x10 + pin);					// calculate port PINCTRL address for pin
	*port_pin_ctrl &= ~(1 << PORT_PULLUPEN_bp);									// disable pull up

}

// initialization functions:
void mcu_init()							// MCU initialization
{
	// On AVR devices all peripherals are enable from power on reset, this
	// disables all peripherals to save power. Driver shall enable
	// peripheral if used

	// Set all pins to low power mode with pull up resistor enabled
	for (uint8_t i = 0; i < 8; i++) {
		*((uint8_t *)&PORTA + 0x10 + i) |= 1 << PORT_PULLUPEN_bp;
	}
}

void pin_init(){ 						// PIN initialization
	VPORTA.DIR |= (1<<PWR_bp);			// set pin as output
	disablePortAPullup(PWR_bp);         // disable pull up resistor

	VPORTA.DIR |= (1<<VCC_bp);			// set pin as output
	disablePortAPullup(VCC_bp);         // disable pull up resistor
	VPORTA_OUT |= (1<<VCC_bp);			// set power on for AVR target 

	VPORTA.DIR |= (1<<CTR_bp);			// set pin as output
	disablePortAPullup(CTR_bp);         // disable pull up resistor
}

void system_init(){						// system initialization
	mcu_init();
	pin_init();
}

And lastly the PlatformIO.ini for this :

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:ATtiny402]
platform = atmelmegaavr
board = ATtiny402
framework = arduino
upload_speed = 115200
upload_port = COM12
upload_flags =
    --tool
    uart
    --device
    attiny402
    --uart
    $UPLOAD_PORT
    --clk
    $UPLOAD_SPEED
upload_command = pymcuprog write --erase $UPLOAD_FLAGS --filename $SOURCE

I worried that the Schottky diode in line with 12v (required to stop 5v otherwise still illuminating LED on boost module.) would take too much off the 12v but I tried without the Schottky diode with the full 12v and it made no difference.

I originally tried the circuit with an RC network on DTR to force a reset when DTR rises after programming but having an AVR do this makes for more control options.

Another hunch I am going to explore is forcing a target device power on just before applying the 12v in response to this from the spec sheet “It is recommended to always reset the device before starting the 12V enable sequence.”

I may extend the HV pulse to be more mid range between 100us and 1ms. (This seems to differ from the Technobology write up).

If you have any suggestions as to what to look at next, I’d welcome the input.

Many thanks

I think I have nailed my issue:

I only include this if you are interested and spent a while reading the original post (sorry).

My processing of the rise in DTR after the UPDI programming sequence was wrong headed.

I was looking for DTR going HIGH then waiting a while, then doing a power down cycle of the target device. The problem with this is that DTR goes up and down eg during a program cycle that will fail (eg one of the UPDI lead to the target device is not connected) - this might cause the target device to be powered down just after I’d issued a programming command (be it a chip “–chip-erase-locked-device” or downloading some flash memory) and this would most definitely corrupt the lock bits hence requiring a chip erase.

I need to look for DTR going HIGH and then wait a while to make sure it is HIGH and stable (no more interaction with UPDI programmer) and then do a power cycle of the target device. I should be able to do the whole sequence in a much faster time so would be unlikely to be issuing another command via UPDI before this power reset sequence is over. I think opening up a terminal in PlatformIO (to view debug TTY being transmitted on PA0) would itself generate the necessary DTR toggle to cause the target device to restart - the terminal should then be ready for the debug.

I confirm I have resolved issue.
My code and hardware build now correctly handle power to target AVR.
I was frustrated by need to power cycle target AVR to get it to send debug TTY to PlatformIO serial monitor - what to do? In the end, I added a power cycle process each time my ‘toggle HV’ button is pressed.

If you are interested, final circuit build (pins moved about to make build easier), ATtiny402 source and a photo of final device in action.

Schematic:

Code for ATtiny402:

// ATtiny402_HV_UPDI
// Peter Charles-Jones

// Code Spell Checker Visual Studio extension - provides spell checking for comments
// cSpell:includeRegExp CStyleComment

/* The Environment
This  was designed using PlatformIO (free) which is an extension of Visual Studio (free)
The big advantage to Arduino 1.8.19 (free) is the intellisense editor which can suggest register names, highlight scope errors, rename variables and so on 
Atmel's Studio 7 (free) is worth a mention as it provides a basic set of peripheral setup code via its AtmelSTART projects
It also has a really good GUI fuse editor for older style MCUs eg ATtiny85, Atmega328 etc
*/ 

/* What is the device
See the data sheet here <https://ww1.microchip.com/downloads/en/DeviceDoc/ATtiny202-402-AVR-MCU-with-Core-Independent-Peripherals_and-picoPower-40001969A.pdf>
Series '0' and '1' devices use Universal Programming Debug Interface (UPDI) which uses 1 pin interface plus ground and positive.
Typically you need to build your own UPDI programmer by modifying a standard serial interface module - see the links below.
If you want to use the UPDI pin as a standard GPIO pin, you need to follow the instructions at <http://www.technoblogy.com/show?48MP> 
to create a High Voltage (12v) programmer.
If you do this - suggest you use the UPDI pin as push button input or as debug serial output where either the programmer or serial monitor 
are connected (but not both at the same time).
*/

/* General approach to UPDI
	I purchased an original ATtiny402 direct from Microchip - the exact part code was ATTINY402-SSN. 
	This part was available as it's not automotive spec and comes in a tube so there was no part reel cutting fee.
	Beware buying devices from generic suppliers such as eBay, AliExpress - I purchased devices with duff device ID (and pin multiplex table issues)

	The upload command used pymcuprog which had to be installed within PlatformIO terminal break out rather than directly on PC.

	The ATtiny402 was programmed using a UPDI programmer made from a UTP to serial device with a diode between TXD and RXD.

	--------------------    	                          To Target device
		           	DTR|	                  330 ohm     __________________
		internal    Rx |--------------,--------\/\/------| UPDI------------->
	  Tx---/\/\/\---Tx |-------|<|---'          .--------| Gnd     
	   	resistor    Vcc|---------------------------------| Vcc
	    typ 1-2k    CTS|                     .`          |__________________
	       			Gnd|--------------------'             If you make a 3-pin connector, use this pinout
	--------------------

	Here's a bunch of links that describe this in more detail....
	<https://github.com/SpenceKonde/AVR-Guidance/blob/master/UPDI/jtag2updi.md>
	<https://pypi.org/project/pymcuprog/>
	<https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/PlatformIO.md>
	<https://docs.platformio.org/en/latest/platforms/atmelmegaavr.html#upload-using-pymcuprog-serialupdi>
	<https://community.platformio.org/t/problems-migrating-from-studio-7-to-platformio-attiny402-with-pymcuprog-custom-tool/29352/9>

	I used Studio START code generator to get used to the peripheral configuration code
	I put all the initial set up required in a system_init() routine and put this code at the bottom 
	below the main() loop structure.

	Direct Port Manipulation - Warning

	See what Spence Konde (AKA Dr Azzy) has to say about direct port manipulation 
	<https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/Ref_DirectPortManipulation.md>

	Interrupts and flag setting
	The setting of multiple FLAG bits is not atomic (may be interrupted by an interrupt service routine) and if an interrupt sets a bit during such an update eg 
	FLAG &= ~((1<<night_set_bp)|(1<<detection_set_bp));	
	the bit set by the interrupt would be lost - so we need a separate set of interrupt flags - hence INT_FLAG and this must be defined as volatile

*/
/* Pin Multiplexing summary
pin Pin Name  Other/Special ADC0 AC0  USART0  SPI0  TWI0  TCA0  TCB0  CCL
6   PA0       ~RESET/UPDI   AIN0      XDIR    ~SS                     LUT0-IN0
5   PA1                     AIN1      *TXD    MOSI  SDA   WO1         LUT0-IN1
4   PA2       EVOUT0        AIN2      *RxD    MISO  SCL   WO2         LUT0-IN2
7   PA3       EXTCLK        AIN3 OUT  XCK     SCK         WO0/WO3
8   GND
1   VDD
2   PA6                     AIN6 AINN0 TXD    *MOSI             WO    LUT0-OUT
3   PA7                     AIN7 AINP0 RXD    *MISO       *WO0        LUT1-OUT

Note * indicates alternate pin

TCA0 is the default timer used for millis() and Delay() in the Arduino wrapper.

When designing a thing, you will be constrained by what your MCU pin multiplex table can support. 
Best start with the basics to see if what you are trying to do will fit your MCU
*/
/* Pin information
			    ATtiny402
			     ---u---
    	  	VDD|	    |GND
   	 	PWR PA6|	    |PA3 HVT
  		DTR PA7|	    |PA0 
	  	CTR PA1|		|PA2 VCC
    			 -------
*/

/* HOW TO USE

// Connect the UDPI target device
// Green LED is power to VCC of target AVR
// Red LED is DTR low
// Blue LED (on boost module) indicates use of HV pulse 
 
// If you want to use HV to program the device, press the push button until the Blue LED on boost board is on.

// If you want to use UDPI for program upload to device and debug from device, first 'burn bootloader' so as to configure UPDI pin as GPIO and 
// also use SendOnlySoftwareSerial on target device to output TTY debug through the UPDI pin (PA0 in the case of a target ATtiny402).

// Make sure upload_port and monitor_port in PlatformIO.ini share the same COM port.

// When this controller sees DTR drop from serial to USB adapter, it will generate a short 12v power pulse to target UPDI pin to flip it into programming mode.
// At the end of program upload when DTR returns HIGH, the unit will briefly drop the power to the target device so as to do a POR. This will flip the UPDI pin back to GPIO mode.
// 
// When you open the monitor terminal in PlatformIO, you will need to perform another POR to get the data to appear there. To do this just press the push button on this unit.

// Beware - the target is being powered by a pin on this units AVR (max 30mA) so make sure the target unit design isolates things like servos, WIFi, Bluetooth etc 
// or you will destroy this unit.

// Specifically make sure that any large (eg 100uF) decoupling capacitors are also isolated or the reset power down will be insufficient to cause a POR on teh target device.
*/


// put pre-compile code here:
#define F_CPU 16000000L		// this tells the code the clock is running at 16MHz (it does not define how fast the clock is running)- see CLKCTRL_init()

// the include code tells the compiler to use code in these libraries - generally this makes coding easier
#include <Arduino.h>        // so we can use the Arduino framework - thanks Dr Azzy!

// the define statements tell the compiler to replace the first element with the second 
// Define names for the pins for easy reference

#define CTR_bp 1 // PA1 output to control switch to actually switch in teh HV pulse into the UPDI connector
#define VCC_bp 2 // PA2 output VCC for target so you can control a target power reset
#define HVT_bp 3 // PA3 input HV toggle button - starts off in normal mode (not HV) - an LED hung on VCC_bp pin indicates the power reset
#define PWR_bp 6 // PA6 output Power for boost 12v (so you get a built in 'POWER ON' LED on the module)
#define DTR_bp 7 // PA7 input DTR detector - connected to DTR of serial to USB adapter

#define button_debounce 50				// button debounce (ms)
#define DTR_debounce 10					// signal debounce (ms)- don't  want to delay application of HV pulse when DRT drops and it won't need debouncing
#define HV_pusle_time_max 500			// HV pulse duration (us) [value of 500 actually measured pulse at CTR / PA1 of 520us]
#define DTR_high_time_max 200 			// delay after DRT raise before power reset to target AVR (ms) The timer is reset while FLAG: Prog_bp is set
#define VCC_off_time_max 10			// power down period for target AVR after post prog delay (ms)

// bit definitions for FLAG - an 8 bit byte, each bit is used to flag status info to the main code
#define HV_Power_On_bp 0 					// FLAG :HV powered up
#define DTR_On_bp 1							// FLAG: set when DTR is HIGH
#define Reset_sequence_bp 2					// FLAG: set when a reset pulse has been applied

// variables used by setup() and loop() - these functions are specific to Arduino. 

// global variable declaration
uint8_t tempPINA = VPORTA_IN;                  							// used as snapshot for PINA status if it changes mid process
uint8_t pinAHistory = VPORTA_IN;                       					// the last read PINA status
uint8_t debounced_PINA = VPORTA_IN;										// debounced copy of the PINA register
uint8_t FLAG;															// flags for program control - see above bit definitions 

unsigned long last_HVT_time = millis();                					// debounce timer (ms)
unsigned long last_DTR_time = millis();                					// debounce timer (ms)
unsigned long last_VCC_time = millis();
unsigned long HV_pulse_time = micros();									// HV pulse timer (us)
unsigned long DTR_state_change_time = millis();									// DTR HIGH timer (ms)
unsigned long VCC_off_time = millis();									// target AVR power off pulse timer (ms)

// put function declarations here: - a 'place holder' allowing the complier to reference functions before they have been fully defined.
int pinChange (unsigned long* last_time, int pinID, int debounce);		// has pin changed?
int pinHIGH (int pinID);												// is pin HIGH?
void system_init();   													// initializes the peripherals 

void setup() {
  	// put your setup code here, to run once:
	system_init();
}

void loop() {
	// put your main code here, to run repeatedly:
		
	// check the pins for any changes
	tempPINA = VPORTA_IN;                             					// get a snapshot of current PIN setting

	// Check HVT for a key press to control whether HV programming mode is set (should light up indicator on boost convertor module)
	if (pinChange(&last_HVT_time, HVT_bp, button_debounce)){
		if (!(pinHIGH(HVT_bp))){               							// HVT is LOW - CC - button pressed
			if(!(FLAG & (1<<HV_Power_On_bp))){
				FLAG |= (1<<HV_Power_On_bp);
				VPORTA_OUT |= (1<<PWR_bp);								// power up the 12v boost board
			}
			else {
				FLAG &= ~(1<<HV_Power_On_bp);
				VPORTA_OUT &= ~(1<<PWR_bp);								// power down the 12v boost board
			}

			FLAG |= (1 << Reset_sequence_bp);								// start the reset sequence				
			VCC_off_time = millis();										// reset the VCC off timer
			VPORTA_OUT &= ~(1 << VCC_bp);									// switch AVR off
    	}
	}

// Check DTR for a key press 
	if (pinChange(&last_DTR_time, DTR_bp, DTR_debounce)){
		DTR_state_change_time = millis();								// restart the DTR high timer each time DTR goes HIGH
		if (!(pinHIGH(DTR_bp))){               							// DTR is LOW - CC - UPDI programming in progress	
			FLAG &= ~(1<<DTR_On_bp);									// DTR is LOW
			VPORTA_OUT |= (1<<VCC_bp);										// switch AVR back on
			if (FLAG & (1<<HV_Power_On_bp)){								// HV programming so send HV pulse
				HV_pulse_time = micros();							// start HV pulse
				VPORTA_OUT |= (1<<CTR_bp);							// switch the 12v onto UDPI output - HV pulse in progress
			}
		}
		else{															// DTR has gone HIGH - need to test if it is stable
			
			FLAG |= (1<<DTR_On_bp);										// set DTR_on flag
			FLAG &= ~(1<<Reset_sequence_bp);							// stop the reset sequence
			VPORTA_OUT |= (1<<VCC_bp);	

		}
	}
	pinAHistory = tempPINA; 											// update the PINA history with the latest snapshot of PINA

	if (VPORTA_OUT & (1<<CTR_bp)){										// if an HV pulse is in progress
		if ((micros() - HV_pulse_time) >= HV_pusle_time_max){			// time to end the HV pulse (allowed value range 10ns to 4ms - we have 127us)
			VPORTA_OUT &= ~(1<<CTR_bp);									// switch 12 v out
		}	
	}		

	
	if ((FLAG & (1<<DTR_On_bp)) && (!(FLAG & (1<<Reset_sequence_bp)))){												// only if DTR is on
		if ((millis() - DTR_state_change_time) >= DTR_high_time_max){ 		// measure how long DTR has been on for
			FLAG |= (1 << Reset_sequence_bp);								// start the reset sequence				
			VCC_off_time = millis();										// reset the VCC off timer
			VPORTA_OUT &= ~(1 << VCC_bp);									// switch AVR off
		}
	}

	if ((FLAG & (1<<Reset_sequence_bp))){									// if a reset sequence is in progress
		if((millis() - VCC_off_time) >= VCC_off_time_max){					// time to stop reset
			VPORTA_OUT |= (1<<VCC_bp);										// switch AVR back on
		}
	}
}

// put function definitions here:	

int pinChange (unsigned long* last_time, int pinID, int debounce){ 		// has the pin changed state?
	if ((tempPINA & (1<<pinID))!= (pinAHistory & (1<<pinID) )){			// last pin state different
    	*last_time = millis();
  	}
	if ((millis() - *last_time) > debounce){							// debounce time has elapsed
		if ((tempPINA & (1<<pinID))!=(debounced_PINA & (1<<pinID))){	// is the current pin state different from the current one
			debounced_PINA &= ~(1<<pinID);								// mask out the bit position
			debounced_PINA |= (tempPINA & (1<<pinID));					// get the bit value at bit location and OR into PINS
    		return 1;													// pin has changed
    	}
  	}
	return 0;                                                 			// pin has not changed state
}

int pinHIGH (int pinID){												// is the pin HIGH?
	if (debounced_PINA & (1<<pinID)){                                  	// pin value is 1.. switch / button is Open Circuit
    	return 1;                                             			// HIGH
  	}
	else{                                                     			// pin value is 0 .. switch / button is Closed Circuit to ground
    	return 0;                                             			//LOW
	}
}

// Interrupt Service Routines

/*	Helper routines - to make the code more readable */
void disablePortAPullup(uint8_t pin){											// code only does this to ADC pin and this has no interrupt so ignore the ISC code
	uint8_t *port_pin_ctrl = ((uint8_t *)&PORTA + 0x10 + pin);					// calculate port PINCTRL address for pin
	*port_pin_ctrl &= ~(1 << PORT_PULLUPEN_bp);									// disable pull up

}

// initialization functions:
void mcu_init()							// MCU initialization
{
	// On AVR devices all peripherals are enable from power on reset, this
	// disables all peripherals to save power. Driver shall enable
	// peripheral if used

	// Set all pins to low power mode with pull up resistor enabled
	for (uint8_t i = 0; i < 8; i++) {
		*((uint8_t *)&PORTA + 0x10 + i) |= 1 << PORT_PULLUPEN_bp;
	}
}

void pin_init(){ 						// PIN initialization
	VPORTA.DIR |= (1<<PWR_bp);			// set pin as output
	disablePortAPullup(PWR_bp);         // disable pull up resistor

	VPORTA.DIR |= (1<<VCC_bp);			// set pin as output
	disablePortAPullup(VCC_bp);         // disable pull up resistor
	VPORTA_OUT |= (1<<VCC_bp);			// set power on for AVR target 

	VPORTA.DIR |= (1<<CTR_bp);			// set pin as output
	disablePortAPullup(CTR_bp);         // disable pull up resistor
}

void system_init(){						// system initialization
	mcu_init();
	pin_init();
}

Photo:

I actually added some more bits to measure VCC_bp pin has no external voltage on it as I’d hate to see what would happen driving a pin low connected to an external 5v supply. I added a safety serial resistor to the VCC_bp pin and also a 10M pull down so I could measure 0v on open circuit.
I added another LED to CTR_bp pin and I tweaked around with the code to make this flash (and prevent an HV session starting) if external VCC could be detected.
If you really want updates of code, schematic, photo - just send a request in a reply.