Program and test a PIC in a standalone project
This tutorial builds a standalone project that programs a Microchip PIC microcontroller through a PICkit 5, then shows the result on the AT1000 screen. At the end, the AT1000 programs a board at the press of the knob, with no computer connected.
You start from an AT1000 with up-to-date firmware, a PICkit 5, a target board and a PC. The examples use a PIC32MX250F128D: replace its part number with yours where shown.
The project does the following each time it runs:
What you need​
- An AT1000 with up-to-date firmware, and AT1000-Interface on your computer.
- A Microchip PICkit 5 and its USB cable.
- A target board with a programming header (ICSP) for the PICkit 5.
- The firmware to program, as a
.hexfile. - To prepare MPLAB IPE once: an x86-64 Linux PC with Docker or Podman. See Requirements.
Step 1: Connect the hardware​
-
Plug the PICkit 5 into an external USB port of the AT1000.
-
Connect the PICkit 5 to the target's programming header. Pin 1 of the PICkit 5 connector is marked with a triangle.
PICkit 5 pin Signal Target connection 1 MCLR / VPP Reset pin, MCLR2 VDD Target supply voltage 3 GND Ground 4 PGD Programming data, PGEDxorICSPDAT5 PGC Programming clock, PGECxorICSPCLKYour device's datasheet gives the pin numbers of these signals.
-
Power the target. The PICkit 5 uses the VDD pin to sense the target voltage, so connect it even when the target has its own supply.
The AT1000 DUT power supply can power the target, so the application switches it on only while programming. Connect the DUT supply output to the target's VDD and GND, and set POWER_TARGET to true in the application of Step 5. See Main power supply.
Step 2: Create the project​
- In AT1000-Interface, select Standalone projects, then select + to create a project. Choose the Python or Node.js template.
- Run the project in Development mode.
- Open the Access tab and set up SSH: select Publish SSH port, then Import public keys or Set password. See Connect to the project over SSH.
- Check that you can open a terminal in the project, replacing
<serial-number>with your device's serial number:ssh -p 2222 dev@AT1032S-<serial-number>.local
The commands in the next steps run either on your PC or in this project terminal. Each step says which.
Step 3: Install MPLAB IPE in the project​
ipecmd needs MPLAB IPE, which you prepare once on your PC and copy into the project. Follow these sections of Program Microchip MCUs with a PICkit 5:
- Produce MPLAB IPE on your PC. For a PIC32MX, use
--families 32bit. - Keep only the packs you need. For a PIC32MX250F128D, keep
PICkit5_TPandPIC32MX_DFP. - Copy it into your project.
Step 4: Copy the firmware and try ipecmd​
- On your PC, copy your firmware into the project's
/workspacefolder asfirmware.hex:scp -P 2222 my-firmware.hex dev@AT1032S-<serial-number>.local:/workspace/firmware.hex - In the project terminal, check that the PICkit 5 is detected:
The output lists it with its serial number:ipecmd -TAvailable Tool List-------------------1 PICkit 5 S.No : 020026702RYN021240
- Check that the PICkit 5 reaches the target:
The output ends with:ipecmd -TPPK5 -P32MX250F128D -ITarget voltage detectedTarget device PIC32MX250F128D found.Device Revision Id = 0xA1Device Id = 0x4d04053Operation Succeeded
If a command fails, see Troubleshooting.
Before programming a board for the first time, you can save what it holds: ipecmd -TPPK5 -P32MX250F128D -GFbackup.hex.
Step 5: Write the application​
The application runs ipecmd to program and verify the target, then shows the result. It reports PASS only when ipecmd exits with code 0 and its output contains Program Succeeded.: ipecmd can exit with 0 without programming anything, for example when the target has no power. A run that takes longer than TIMEOUT_S is stopped and reported as FAIL.
-OL releases the target from reset at the end, so the new firmware starts running. Without it, the target stays in reset.
Set PART to your part number, without the PIC prefix.
- NodeJS
- Python
In the project terminal, replace the contents of /workspace/src/index.ts with:
// Program a PIC with the PICkit 5, then show PASS or FAIL on the AT1000 screen.
import { execFile } from 'node:child_process';
import { AT1000 } from '@ikalogic/at1000';
const PART = '32MX250F128D'; // target part number, without the "PIC" prefix
const HEX_FILE = 'firmware.hex'; // firmware to program, in /workspace
const TIMEOUT_S = 120; // longest time a programming run may take
// Set to true if the target is powered by the AT1000 DUT supply.
const POWER_TARGET = false;
const TARGET_VOLTAGE = 3.3;
// Run ipecmd. Resolve to { passed, message }.
function programTarget(): Promise<{ passed: boolean; message: string }> {
const args = ['-TPPK5', `-P${PART}`, '-M', `-F${HEX_FILE}`, '-OL'];
return new Promise((resolve) => {
execFile('ipecmd', args, { timeout: TIMEOUT_S * 1000 }, (error, stdout, stderr) => {
const output = stdout + stderr;
console.log(output);
if (error?.killed) {
resolve({ passed: false, message: `No answer after ${TIMEOUT_S} s` });
return;
}
// ipecmd can exit with 0 when nothing was programmed: check the output too.
if (!error && output.includes('Program Succeeded.')) {
resolve({ passed: true, message: 'Program Succeeded.' });
return;
}
// Report the last line ipecmd printed, skipping its timestamped log lines.
const lines = output.split('\n').filter((line) => line.trim() && !line.startsWith('['));
resolve({ passed: false, message: lines.at(-1) ?? `ipecmd exit code ${error?.code}` });
});
});
}
let deviceAddress: string | undefined;
if (AT1000.isStandalone()) {
deviceAddress = await AT1000.findLocalDevice();
} else {
const devices = await AT1000.findDevices();
deviceAddress = devices[0];
}
if (!deviceAddress) {
console.log('No devices found');
process.exit(1);
}
const tester = await AT1000.open(deviceAddress);
const screen = tester.hmi.screen();
const speaker = tester.hmi.audio();
const dut = tester.power.dut(0);
await screen.clear();
await screen.print('Programming...');
if (POWER_TARGET) {
await dut.enable(TARGET_VOLTAGE);
await new Promise((resolve) => setTimeout(resolve, 500)); // let the target's supply settle
}
let result;
try {
result = await programTarget();
} finally {
if (POWER_TARGET) {
await dut.disable();
}
}
await screen.clear();
if (result.passed) {
await screen.print(`PASS\n${result.message}`);
await speaker.play({ sound_id: 'success', volume: 40 });
} else {
await screen.print(`FAIL\n${result.message}`);
await speaker.play({ sound_id: 'failure', volume: 40 });
}
console.log(result.passed ? 'PASS' : 'FAIL', result.message);
process.exit(result.passed ? 0 : 1);
In the project terminal, replace the contents of /workspace/main.py with:
"""Program a PIC with the PICkit 5, then show PASS or FAIL on the AT1000 screen."""
import subprocess
import time
from ikalogic_at1000 import AT1000
PART = "32MX250F128D" # target part number, without the "PIC" prefix
HEX_FILE = "firmware.hex" # firmware to program, in /workspace
TIMEOUT_S = 120 # longest time a programming run may take
# Set to True if the target is powered by the AT1000 DUT supply.
POWER_TARGET = False
TARGET_VOLTAGE = 3.3
def program_target():
"""Run ipecmd. Return (passed, message)."""
command = ["ipecmd", "-TPPK5", f"-P{PART}", "-M", f"-F{HEX_FILE}", "-OL"]
try:
result = subprocess.run(command, capture_output=True, text=True, timeout=TIMEOUT_S)
except subprocess.TimeoutExpired:
return False, f"No answer after {TIMEOUT_S} s"
output = result.stdout + result.stderr
print(output)
# ipecmd can exit with 0 when nothing was programmed: check the output too.
if result.returncode == 0 and "Program Succeeded." in output:
return True, "Program Succeeded."
# Report the last line ipecmd printed, skipping its timestamped log lines.
lines = [line for line in output.splitlines() if line.strip() and not line.startswith("[")]
return False, lines[-1] if lines else f"ipecmd exit code {result.returncode}"
if AT1000.is_standalone():
device_address = AT1000.find_local_device()
else:
devices = AT1000.find_devices()
device_address = devices[0] if devices else None
if not device_address:
print("No devices found")
raise SystemExit(1)
tester = AT1000.open(device_address)
screen = tester.hmi.screen()
speaker = tester.hmi.audio()
dut = tester.power.dut(0)
screen.clear()
screen.print("Programming...")
if POWER_TARGET:
dut.enable(TARGET_VOLTAGE)
time.sleep(0.5) # let the target's supply settle
try:
passed, message = program_target()
finally:
if POWER_TARGET:
dut.disable()
screen.clear()
if passed:
screen.print(f"PASS\n{message}")
speaker.play(sound_id="success", volume=40)
else:
screen.print(f"FAIL\n{message}")
speaker.play(sound_id="failure", volume=40)
print("PASS" if passed else "FAIL", message)
raise SystemExit(0 if passed else 1)
Step 6: Run it from the terminal​
In the project terminal, run the application:
- NodeJS
- Python
cd /workspace
npm start
cd /workspace
uv run main.py
The AT1000 screen shows Programming.... About 15 seconds later, it shows PASS and plays the success sound. The terminal shows the full ipecmd output, ending with:
Program Succeeded.
Operation Succeeded
PASS Program Succeeded.
Step 7: Run it from the front panel​
-
In AT1000-Interface, open the project's Overview tab and set the Startup command:
- NodeJS
- Python
npm startuv run main.pySelect Save.
-
In the project header, select Normal, then Run. The project programs the target and shows the result.
-
To program the next board, connect it, then select the project in the front-panel project menu with the rotary knob and press the knob.
Select Default project on the Overview tab to run the project automatically each time the AT1000 starts. See Standalone projects.
When programming fails​
On a failure, the screen shows FAIL with the last message from ipecmd, and the AT1000 plays the failure sound. The application exits with code 1, and the project's Logs tab shows the full ipecmd output.
| Screen message | Meaning |
|---|---|
FAIL Connection Failed. | The PICkit 5 cannot reach the target: check its power and the programming connections. |
FAIL No Tools Connected. | The PICkit 5 is not detected: check its USB cable. |
FAIL Hex File could not be read. | firmware.hex is missing from /workspace. |
FAIL No answer after 120 s | ipecmd did not finish in time. |
For other messages, see Troubleshooting.
Next steps​
- Program several boards one after another: wrap the programming part in a loop that waits for a knob press, as in the project template. See HMI.
- Test the board after programming it: measure its supply current with the DUT power supply, or check its outputs with the digital I/O.