pico6502 Developer's Guide

Note: The pico6502 project is in early development. The driver framework and memory model are fully implemented and share the same architecture as the picoZ80. However, only a placeholder BBC Model B persona exists at this time — this guide explains how to build upon that foundation to add your own drivers. Where the pico6502 is architecturally identical to the picoZ80, cross-references to the picoZ80 Developer's Guide are provided.

Overview

The pico6502 firmware is structured identically to the picoZ80 firmware. It uses the same dual-core RP2350B architecture, the same PSRAM memory model, the same JSON configuration system, and the same driver framework — the primary differences are:
  • M6502CPU.c / M6502CPU.h replace Z80CPU.c / Z80CPU.h as the main dispatch and driver-framework files.
  • The 6502 has no separate I/O address space — there is no ioPtr[] array and no iomap JSON section. All peripherals are virtualised as FUNC-type blocks in the 64KB memory map.
  • The PIO bus interface (M6502.pio) implements dual-phase PHI1/PHI2 clocking rather than the Z80 single-clock model.
  • The build target directory is BaseM6502 rather than BaseZ80.
  • The JSON CPU section key is "6502" rather than "z80".
Everything else — the inter-core queue pattern, driver lifecycle callbacks (reset_ptr, poll_ptr, task_ptr), the virtualFuncMap[] registration table, memory block type constants, the t_6502PSRAM struct, the ESP32 co-processor protocol, and the build system — is shared with the picoZ80 codebase.

Source Tree

All source code lives under projects/tzpuPico/ within the repository root. The layout below highlights files specific to the pico6502:
tzpuPico/
├── CMakeLists.txt                          Top-level build file
├── src/
│   ├── CMakeLists.txt                      Source-level build file — add new driver files here
│   ├── M6502CPU.c                          *** KEY FILE: 6502 dispatch, driver framework, virtualFuncMap
│   ├── M6502CPU.h                          (legacy — included via include/)
│   ├── M6502.c                             PIO initialisation and bus cycle entry points
│   ├── M6502.pio                           PIO programs: clock, addr, data, cycle, fetch, read, write, irq, nmi, so
│   ├── Z80CPU.c / Z80CPU.h                 Z80 counterpart (not used for pico6502 builds)
│   ├── FSPI.c / FSPI.h                     Flash SPI interface (shared)
│   ├── ESP.c / ESP.h                       ESP32 communication layer (shared)
│   ├── psram.c / psram.h                   PSRAM allocation and management (shared)
│   ├── cJSON.c / cJSON.h                   JSON parser for config.json (shared)
│   ├── include/
│   │   ├── M6502CPU.h                      *** KEY FILE: all 6502 type definitions and macros
│   │   ├── M6502.h                         PIO helper definitions
│   │   └── drivers/
│   │       └── BBC/
│   │           └── ModelB.h               BBC Model B driver header (early placeholder)
│   ├── drivers/
│   │   └── BBC/
│   │       └── ModelB.c                   *** EXAMPLE/PLACEHOLDER DRIVER (BBC Model B persona)
│   └── model/
│       ├── BaseM6502/
│       │   ├── CMakeLists.txt             Per-model build targets
│       │   ├── main.c                     Entry point (Core 0 + Core 1 launch)
│       │   ├── main_memmap_partition_1.ld Linker script for Slot 1
│       │   └── main_memmap_partition_2.ld Linker script for Slot 2
│       └── Bootloader/
│           ├── CMakeLists.txt
│           └── main.c                     Shared bootloader (same as picoZ80)
└── esp32/
    ├── CMakeLists.txt
    └── main/
        └── ...                            ESP32 firmware (shared with picoZ80)

Key Differences from picoZ80

No Separate I/O Address Space

The most important architectural difference is that the 6502 has no IN/OUT instructions and no IORQ signal. Every peripheral must appear somewhere in the 64KB memory map. In the picoZ80, virtual devices can be registered via either memioPtr[] (memory-mapped) or ioPtr[] (I/O-mapped). In the pico6502 there is only memioPtr[] — but since all peripheral accesses come through the memory bus, this covers all cases.
The t_6502PSRAM struct therefore has no ioPtr[] array. The JSON configuration has no "io" array. The dispatch loop in M6502CPU.c checks only the _membankPtr[] array and, for MEMBANK_TYPE_FUNC blocks, calls the corresponding memioPtr[] handler.

PIO Bus Interface Differences

The 6502 uses a two-phase non-overlapping clock scheme. PHI0 is an external clock input; the RP2350 generates PHI1 and PHI2 from it via a dedicated PIO 2 state machine (m6502_clock_6502). This is different from the Z80, which uses a single CLK signal.
PIO Block State Machines Purpose
PIO 0 m6502_addr, m6502_data Address bus (A0–A15), data bus (D0–D7)
PIO 1 m6502_cycle, m6502_fetch, m6502_read, m6502_write, m6502_irq, m6502_nmi, m6502_so Bus cycle sequencing and control signal monitoring
PIO 2 m6502_clock_6502 PHI1/PHI2 two-phase clock generation from external PHI0
The IRQ convention differs slightly from the Z80 PIO interface:
IRQ Flag Signal Meaning
IRQ 0 Address/cycle start New 6502 bus cycle — address valid on PHI1 rising edge
IRQ 1 Data phase Data bus active — read or write depending on RNW
IRQ 4 NMI Non-Maskable Interrupt asserted
IRQ 5 IRQ Maskable Interrupt asserted
IRQ 6 SO Set Overflow pin asserted
IRQ 7 Loop exit Core 1 execution loop exit request

PIO Programming — How C Code Drives Bus Cycles

The pico6502 uses the same out exec mechanism as the picoZ80 for dynamic instruction injection, but with 32-bit instructions (out exec, 32) instead of 16-bit. The C code on Core 1 pushes pre-encoded PIO instruction sequences into the cycle SM's TX FIFO to control each bus transaction.
The coordination flow for a 6502 memory read cycle:
                  Core 1 (C code)               PIO State Machines
                  ─────────────                 ──────────────────
    1. Resolve address                          m6502_cycle: IRQ 0 set
       from memory map                           (waiting on PHI2 low)
                  │
    2. Push addr → TX FIFO ──────────────────→ m6502_addr: receives addr
       Clear IRQ 0                               outputs A0–A15 on pins
                  │
    3. Push cycle instructions ──────────────→ m6502_cycle: out exec, 32
       (read sequence) into                       executes: set R/W̄ high, SYNC low
       cycle SM TX FIFO                           executes: wait PHI2 high
                  │                               executes: check RDY
    4. Wait for RX FIFO ←────────────────────  m6502_data: samples D0–D7
       (data byte from bus)                       at PHI2 falling edge
                  │
    5. Read data from                          m6502_cycle: JMP start_cycle
       RX FIFO                                    (ready for next cycle)
                  │
    6. Dispatch to PSRAM
       or driver handler
For write cycles, Core 1 pushes the data byte to the m6502_data SM's TX FIFO after the address. The data SM drives D0–D7 during the PHI2 high phase, then tristates the data bus when PHI2 falls. The cycle SM sets R/W̄ low to indicate a write.
For a detailed explanation of PIO fundamentals, the out exec mechanism, and state machine coordination patterns, see the picoZ80 Technical Guide — PIO Architecture Deep Dive. The 6502-specific PIO differences are:
  • 32-bit out exec — allows full PIO instruction encoding including side-set and delay fields in a single FIFO push.
  • Clock-phase synchronisation — cycle SMs synchronise to PHI2 edges (wait 0/1 gpio CLK2) rather than a single CLK signal.
  • No BUSREQ/BUSACK — the 6502 has no bus request mechanism, so there is no equivalent of the Z80 z80_busrq SM.
  • RDY instead of WAIT — wait states are inserted by checking the RDY pin after PHI2 rising edge, rather than the Z80's /WAIT pin at T2 falling edge.

t_6502PSRAM Struct

Equivalent to t_Z80PSRAM in the picoZ80, but without the ioPtr[] array:
// src/include/M6502CPU.h (simplified)
typedef struct {
    uint8_t      RAM[MAX_MEMORY_BANKS * MEMORY_PAGE_SIZE]; // 64 banks × 64KB = 4MB RAM/ROM area
    MemoryFunc   memPtr[MEMORY_PAGE_SIZE];                  // 64K per-byte read redirect (PTR type)
    MemoryFunc   memioPtr[MEMORY_PAGE_SIZE];                // 64K memory-mapped handler array (FUNC type)
    // NOTE: no ioPtr[] — the 6502 has no separate I/O space
} t_6502PSRAM;

Handler Function Signature

The MemoryFunc typedef is the same as for the picoZ80:
// Handler called for MEMBANK_TYPE_FUNC blocks on every access
typedef uint8_t (*MemoryFunc)(M6502CPU *cpu, bool read, uint16_t addr, uint8_t data);

// Parameters:
//   cpu  — pointer to the M6502CPU struct (access PSRAM, state, queues via this)
//   read — true for a 6502 read cycle (RNW=1), false for a write cycle (RNW=0)
//   addr — 16-bit address that triggered this handler
//   data — byte written (only meaningful when read=false)
// Return:
//   byte to place on the data bus (only meaningful when read=true)
Because there is no I/O space, there is no separate IOFunc typedef — MemoryFunc handles all peripheral accesses.

Driver Framework

The driver framework in M6502CPU.c is structurally identical to the picoZ80 driver framework. Refer to the picoZ80 Developer's Guide — Driver Framework section for the full description of:
  • The virtualFuncMap[] registration table and how the JSON "name" field is matched to a driver init function.
  • The VirtualFunc typedef and M6502CPU_getVirtualFunc() lookup.
  • The two-phase init flow (validation pass then configuration pass).
  • Driver lifecycle callbacks: reset_ptr, poll_ptr, task_ptr.
The only naming differences are:
picoZ80 pico6502 Notes
Z80CPU struct M6502CPU struct Same fields, same layout
Z80CPU_getVirtualFunc() M6502CPU_getVirtualFunc() Same lookup logic
Z80CPU_cpu() M6502CPU_cpu() Core 1 hot loop entry point
Z80CPU_readMem() M6502CPU_readMem() Memory read dispatch
Z80CPU_writeMem() M6502CPU_writeMem() Memory write dispatch
Z80CPU_readIO() No equivalent — 6502 has no I/O space
Z80CPU_writeIO() No equivalent — 6502 has no I/O space
cpu->_z80PSRAM cpu->_6502PSRAM PSRAM struct pointer

Core 1 Dispatch Loop

The Core 1 hot loop in M6502CPU_cpu() follows the same pattern as the picoZ80. On each 6502 bus cycle:
  1. Wait for IRQ 0 (address valid on PHI1).
  2. Read the 16-bit address from the PIO 0 RX FIFO.
  3. Look up _membankPtr[addr >> 9] to get the block type, bank, and base.
  4. Determine RNW (read/not-write) from the control PIO.
  5. Dispatch based on block type: PHYSICAL (pass to host), RAM (PSRAM read/write), ROM (PSRAM read only), FUNC (call memioPtr[addr]), PTR (redirect via memPtr[addr]).
  6. If read: place result on data bus via PIO 0 TX FIFO. Wait for IRQ 1 (data phase). If write: read data from PIO 0 RX FIFO after IRQ 1.
  7. Call poll_ptr for each registered driver (every ~2048 cycles).
// Simplified M6502CPU_readMem / M6502CPU_writeMem dispatch
uint8_t M6502CPU_readMem(M6502CPU *cpu, uint16_t addr)
{
    uint32_t entry = cpu->_membankPtr[addr >> 9];
    uint8_t  type  = (entry >> 24) & 0xFF;
    uint8_t  bank  = (entry >> 16) & 0xFF;
    uint32_t base  = (entry & 0xFFFF) << 9;

    switch(type)
    {
        case MEMBANK_TYPE_RAM:
        case MEMBANK_TYPE_ROM:
            return cpu->_6502PSRAM->RAM[(bank * MEMORY_PAGE_SIZE) + addr];

        case MEMBANK_TYPE_FUNC:
            if(cpu->_6502PSRAM->memioPtr[addr] != NULL)
                return cpu->_6502PSRAM->memioPtr[addr](cpu, true, addr, 0);
            return 0x00;

        case MEMBANK_TYPE_PTR:
            if(cpu->_6502PSRAM->memPtr[addr] != NULL)
                return cpu->_6502PSRAM->memPtr[addr](cpu, true, addr, 0);
            return 0x00;

        case MEMBANK_TYPE_PHYSICAL:
        default:
            return 0xFF;  // Pass-through to real host hardware
    }
}

Writing a New Driver

The process for writing a pico6502 driver is identical to the picoZ80 process described in the picoZ80 Developer's Guide — Writing a New Driver. The six steps are the same:
  1. Create a header file in src/include/drivers/<Family>/MyDriver.h
  2. Create an implementation file in src/drivers/<Family>/MyDriver.c
  3. Add the source file to src/CMakeLists.txt
  4. Include the driver in M6502CPU.c under a #ifdef INCLUDE_<FAMILY>_DRIVERS guard
  5. Register the driver in virtualFuncMap[] in M6502CPU.c
  6. Add a JSON driver entry in config.json
The key difference is that instead of a Z80CPU *cpu parameter, all functions receive an M6502CPU *cpu parameter, and you access PSRAM via cpu->_6502PSRAM rather than cpu->_z80PSRAM. There are no ioPtr[] slots to install — all virtual peripherals are registered in memioPtr[].

Minimal Driver Template

The following shows a minimal 6502 peripheral driver — a virtualised 6522 VIA (Versatile Interface Adapter) at address 0xC000–0xC00F:
// src/include/drivers/BBC/VIA6522.h
#ifndef VIA6522_H
#define VIA6522_H

#include "M6502CPU.h"

int VIA6522_Init(M6502CPU *cpu, t_drvConfig *drvConfig, int pass);

#endif
// src/drivers/BBC/VIA6522.c
#include "../../include/M6502CPU.h"
#include "../../include/drivers/BBC/VIA6522.h"

// --- Internal state ---
typedef struct {
    uint8_t  regs[16];      // VIA internal registers (ORB, ORA, DDRB, DDRA, T1CL, T1CH, ...)
    bool     irqPending;
} t_VIA6522State;

static t_VIA6522State ViaState = { .irqPending = false };

// --- Memory handler (called for every access to 0xC000–0xC00F) ---
static uint8_t VIA6522_Handler(M6502CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
    uint8_t reg = addr & 0x0F;   // VIA has 16 internal registers

    if(read)
    {
        return ViaState.regs[reg];
    }
    else
    {
        ViaState.regs[reg] = data;
        // TODO: implement timer, shift register, port logic as needed
        return 0;
    }
}

// --- Reset handler (called when host system resets) ---
static uint8_t VIA6522_Reset(M6502CPU *cpu)
{
    memset(&ViaState.regs, 0, sizeof(ViaState.regs));
    ViaState.irqPending = false;
    return 0;
}

// --- Init function (called during JSON config parse) ---
int VIA6522_Init(M6502CPU *cpu, t_drvConfig *drvConfig, int pass)
{
    if(pass == 0)
    {
        // Validation pass — just check the name matches
        return (strcasecmp(drvConfig->name, "via6522") == 0) ? 1 : 0;
    }

    // Configuration pass — install handlers
    // Install the handler for all 16 VIA registers at 0xC000–0xC00F
    for(uint16_t addr = 0xC000; addr <= 0xC00F; addr++)
    {
        cpu->_6502PSRAM->memioPtr[addr] = VIA6522_Handler;
    }

    // Register reset callback
    cpu->reset_ptr = VIA6522_Reset;

    debugf("VIA6522: Initialised at 0xC000–0xC00F\n");
    return 1;
}

JSON Configuration

To activate the VIA6522 driver, add a FUNC block to the memory map and a driver entry:
{
  "rp2350": {
    "6502": {
      "memory": [
        { "enable":1, "addr":"0x0000", "size":"0xC000",
          "type":"RAM",  "bank":0, "task":"", "file":"" },
        { "enable":1, "addr":"0xC000", "size":"0x0010",
          "type":"FUNC", "bank":0, "task":"via6522", "file":"" },
        { "enable":1, "addr":"0xE000", "size":"0x2000",
          "type":"ROM",  "bank":0, "task":"", "file":"/ROM/bios.rom" }
      ],
      "drivers": [
        { "enable":1, "name":"via6522" }
      ]
    }
  }
}

Registering in virtualFuncMap

Add an entry to the virtualFuncMap[] table in M6502CPU.c. The name string is matched case-insensitively against the JSON driver "name" field:
// In M6502CPU.c — add to virtualFuncMap[]
static const t_VirtualFuncMap virtualFuncMap[] = {
    { "BBCModelB", BBCModelB_Init },   // existing entry
    { "via6522",   VIA6522_Init   },   // your new driver
    { NULL, NULL }                     // terminator
};

CMakeLists.txt Change

Add the new source file and enable it via a compile definition in src/CMakeLists.txt:
# In src/CMakeLists.txt
target_sources(tzpuPico PRIVATE
    ...
    drivers/BBC/ModelB.c        # existing
    drivers/BBC/VIA6522.c       # your new driver
)

target_compile_definitions(tzpuPico PRIVATE
    INCLUDE_BBC_DRIVERS=1
)

Build and Test

# From the project root
./build_tzpuPico.sh

# Flash via USB mass-storage
cp build/bin/model/BaseM6502/BaseM6502_0x10020000.uf2 /media/$USER/RPI-RP2/

# Or OTA: upload via http://<device-ip>/ota-rp2350.htm

Memory Hook Patterns

Because the 6502 has no I/O space, all hook patterns use memioPtr[]. The patterns are the same as those described in the picoZ80 Developer's Guide — Memory Hook Patterns, with the I/O port handler pattern replaced by a memory-mapped handler at any address range. The key patterns for 6502 drivers are:

FUNC Block — Virtual Peripheral

Install a handler for a contiguous address range. This is the standard pattern for all 6502 peripheral emulation:
// Install handler for address range 0xD000–0xD0FF (256-byte peripheral block)
for(uint16_t addr = 0xD000; addr <= 0xD0FF; addr++)
{
    cpu->_6502PSRAM->memioPtr[addr] = MyPeripheral_Handler;
}

RAM Write Intercept

Intercept writes to a RAM region while still allowing reads from PSRAM. Useful for shadowing zero-page accesses or detecting writes to specific addresses:
// Handler intercepts writes; passes reads to PSRAM
static uint8_t ZeroPage_Intercept(M6502CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
    if(read)
    {
        // Read from PSRAM normally
        return cpu->_6502PSRAM->RAM[addr];
    }
    else
    {
        // Write to PSRAM and update shadow state
        cpu->_6502PSRAM->RAM[addr] = data;
        MyDriver_OnZeroPageWrite(addr, data);
        return 0;
    }
}

// In init: set the block type to FUNC and install the handler
cpu->_membankPtr[0x0000 >> 9] = MEMBANK_ENCODE(MEMBANK_TYPE_FUNC, 0, 0x0000);
cpu->_6502PSRAM->memioPtr[0x0000] = ZeroPage_Intercept;
// ... repeat for each 512-byte block in the range

Sparse / Individual Addresses

For hardware where only a few addresses matter within a larger region (e.g. a 4-bit address decoder), install the handler only on those specific addresses and let the rest return 0xFF or PSRAM data:
// Only addresses 0xFFFE and 0xFFFF (6502 reset vector) are intercepted
cpu->_6502PSRAM->memioPtr[0xFFFE] = ResetVector_Lo_Handler;
cpu->_6502PSRAM->memioPtr[0xFFFF] = ResetVector_Hi_Handler;

BBC Model B Persona (Placeholder)

The only driver currently present in the pico6502 firmware is the BBC Model B persona (src/drivers/BBC/ModelB.c). This is an early-stage placeholder that demonstrates the driver registration pattern but does not yet implement the full BBC Micro hardware — the 6845 CRTC, 6522 VIAs, 6850 ACIA, SAA5050 teletext chip, and ULA are not virtualised.
The BBC Model B is an appropriate first target for the pico6502 because it uses a standard 6502 at 2MHz, has well-documented hardware, and relies entirely on memory-mapped peripherals — which aligns naturally with the pico6502's FUNC-based peripheral model. When the persona is complete, it will need to virtualise the following at minimum:
Address Width Chip Function
0xFC00–0xFCFF 256B 6845 CRTC Video timing / memory address generator
0xFE00–0xFE0F 16B 6522 VIA A System VIA (keyboard, sound, RTC)
0xFE40–0xFE4F 16B 6522 VIA B User VIA (parallel port, user I/O)
0xFE60–0xFE6F 16B 6850 ACIA Serial interface
0xFC00–0xFCFF 256B SAA5050 / ULA Teletext / video ULA
0xFE30 1B ROM select Sideways ROM bank select register

Core 0 / Core 1 Interaction

The inter-core queue pattern is identical to the picoZ80. Refer to the picoZ80 Developer's Guide — Core 0 / Core 1 Interaction for the full description and code example. The only naming difference is that the queue is accessed via an M6502CPU *cpu pointer rather than Z80CPU *cpu.
Key rule: handler and poll callbacks run on Core 1. Any file I/O, UART command to the ESP32, or other blocking operation must be posted to Core 0 via cpu->requestQueue. Results are returned via cpu->responseQueue and processed in task_ptr.

Common Pitfalls

  • Installing a handler in ioPtr instead of memioPtr. The pico6502 has no ioPtr[] array. All peripheral handlers go in memioPtr[]. Attempting to use ioPtr[] will cause a build error or undefined behaviour.
  • Expecting an I/O dispatch call. There is no M6502CPU_readIO() or M6502CPU_writeIO(). If you are porting a Z80 driver that uses ioPtr[], move all those handlers to memioPtr[] at the appropriate memory addresses.
  • Blocking in a handler. Any call to debugf, sleep_ms, or fopen from inside a handler will stall Core 1 and corrupt 6502 bus timing. Use the inter-core queue for all blocking operations.
  • Mismatched virtualFuncMap name. The JSON "name" field is matched case-insensitively against the virtualFuncMap[] table in M6502CPU.c. A typo in either will silently skip the driver. Add a temporary debugf in M6502CPU_getVirtualFunc() to diagnose.
  • Off-by-one in handler range. Use addr <= endAddr not addr < endAddr + 1 — the latter risks integer overflow at 0xFFFF. Use addr <= 0xFFFF with a uint32_t loop variable if the range extends to the top of memory.
  • Forgetting to set the block type to FUNC. Installing a handler in memioPtr[] has no effect if the corresponding _membankPtr[] entries still show MEMBANK_TYPE_RAM or MEMBANK_TYPE_ROM. Always call MEMBANK_ENCODE(MEMBANK_TYPE_FUNC, ...) for each 512-byte block in your handler range.
  • reset_ptr not cleared on driver shutdown. If your driver is reconfigured at runtime and the reset callback is no longer valid, clear cpu->reset_ptr to NULL before re-initialising, otherwise Core 1 will call a stale function pointer on the next reset.

Reference Sites

Resource Link
pico6502 project page /pico6502/
pico6502 User Manual /pico6502-usermanual/
pico6502 Technical Guide /pico6502-technicalguide/
picoZ80 Developer’s Guide /picoz80-developersguide/
picoZ80 project page /picoz80/
RP2350 Datasheet datasheets.raspberrypi.com
Pico SDK Multicore API raspberrypi.github.io/pico-sdk-doxygen
MOS 6502 Datasheet archive.org
BBC Micro Hardware Guide stardot.org.uk
cJSON Library github.com/DaveGamble/cJSON

Wireless Regulatory Notice

This device incorporates an ESP32-S3-PICO-1 wireless module that transmits in the 2.4 GHz ISM band, making it an intentional radiator under radio-frequency regulations worldwide (including FCC Part 15 Subpart C in the United States, and the Radio Equipment Directive 2014/53/EU in the European Union).
Although the ESP32-S3-PICO-1 module itself carries pre-existing regulatory certifications (FCC, CE, and others), those module-level certifications do not automatically extend to a finished product that incorporates the module. The pre-certified module exemption permits individual hobbyists to build a limited number of devices for personal, experimental, or educational use without obtaining separate equipment authorisation.
Important Limitations
  • Assembled devices must not be sold, offered for sale, gifted, or otherwise distributed to third parties unless the finished product has been independently tested and granted its own equipment authorisation (e.g. FCC ID, CE marking with a Notified Body assessment) in the relevant jurisdiction.
  • Building this project for personal use in limited quantities is generally permitted under hobbyist and experimental-use provisions (e.g. FCC § 15.23), provided the device does not cause harmful interference.
  • Regulatory requirements vary by country. Builders outside the United States should consult their national radio-frequency authority for applicable rules.
Builder’s Responsibility
It is the builder’s sole responsibility to ensure that any device constructed from these designs complies with all applicable radio-frequency regulations in their jurisdiction. The author provides these designs for personal, educational, and hobbyist use and makes no representation that a device built from them satisfies the regulatory requirements for commercial distribution.