This layer targets AVR microcontrollers (Arduino Uno and other ATmega/ATtiny boards). Some
symbols in micropython-rp2-stubs (the parity suite's reference, tests/parity/) describe
hardware, a runtime, or a MicroPython stub-authoring convention that does not apply to this
class of chip. Each section below is quoted by tests/parity/allowlist.toml for the symbols
it covers.
network is not implemented: this class of AVR chip has no radio hardware, so network.WLAN,
network.LAN, network.PPP and the module-level network helpers and constants have nothing to
control.
rp2 targets the RP2040/RP2350 PIO state machines and DMA channels, neither of which exists on
this class of AVR chip, so rp2.PIO, rp2.StateMachine, rp2.DMA and the module-level rp2
helpers are not implemented.
framebuf is a builtin of the MicroPython interpreter, written in C
(extmod/modframebuf.c), so there is no upstream Python source to vendor: this layer
re-expresses that C module. What it draws is pinned against the real interpreter, probe by
probe, in tests/test_framebuf.py: MONO_VLSB, MONO_HLSB, MONO_HMSB, GS2_HMSB, GS4_HMSB,
GS8 and RGB565, with the clipping, the per-format stride rounding and the out-of-bounds
answers. The buffer stays the caller's, as upstream: a FrameBuffer never allocates.
FrameBuffer.text() takes a string known at compile time. Upstream walks the characters of
any string at run time; the compiler has no run-time string iteration, so the walk is
unrolled while compiling. A string that differs between paths is refused, naming the loop,
instead of drawing something else.
FrameBuffer.ellipse() is refused while PyMCU/PyMCU#510 is open, and the refusal says so. The walk is
implemented and draws what the interpreter draws -- under CPython, and on the board
whenever the program calls ellipse() from more than one place. With a single call site
the compiler inlines the method instead of emitting it as a subroutine, and the inlined
filled walk writes different pixels: 11 bytes of 256 for
ellipse(30, 15, 10, 8, 1, True) on a 64x32 MONO_VLSB buffer, with nothing said. The
other ten primitives were each measured with a single call site and are correct. Drawing
almost the right ellipse in silence is worse than refusing, so this refuses until the
inliner is fixed, and the diagnostic names the issue and the workaround;
tests/framebuf/withheld/ keeps the probe and the interpreter's output
for the day it is lifted.
FrameBuffer.poly() is refused with a diagnostic. Its outline walk indexes an array of
coordinates at run time and its filled walk needs one array of scan-line crossings per
polygon, sized at run time, and there is no heap to size it in. FrameBuffer.line() draws
the edges.
FrameBuffer.blit() takes a FrameBuffer, never the (buffer, width, height, format[, stride]) tuple upstream also accepts as the source.
The constructor refuses at compile time what upstream refuses with a ValueError at run
time: a width or height below 1, a stride below width, an unknown format, and a buffer
too small for the geometry. Raising needs a heap, so the check has to happen while the
sizes are still constants, which in the shape people write is exactly when they are:
buf = bytearray(256)
fb = framebuf.FrameBuffer(buf, 64, 32, framebuf.MONO_VLSB) # 256 is the requirementThe size check uses upstream's own formula from framebuf_make_new_helper, per-format
rounding included, and it was measured against the real interpreter over 210 boundary
cases: one byte under and one byte over the exact requirement, in all seven formats, at
five geometries, with and without an explicit stride. The two agree on every one of them.
The twelve cases where they differ are buffers past 2 KB, which the AVR backend refuses
first for not fitting in SRAM, with its own message.
A FrameBuffer built inside a function from a bytearray parameter does not compile,
and did not before this check either: forwarding a buffer through a call into a field loses
it, and the later writes are refused. Build the FrameBuffer where the buffer is named, or
hold the buffer in a field as the drivers do.
Every index is computed in 16 bits, so the addressable buffer stops at 32767 bytes. That covers every mono display and every small colour one; a 320x240 RGB565 frame is past it.
FrameBuffer1 is a subclass of FrameBuffer here and a factory function upstream, so
type() answers differently and nothing that draws does.
Every drawing method is positional-only upstream. A / in a def is syntax the compiler
refuses, so the whole surface differs from the stub by that marker and by nothing else:
the parameter names, their order and their defaults all match. Tracked by
#19, the same gap as machine.Pin.irq.
machine.ADC's attenuation (ATTN_*) and resolution (WIDTH_*) controls, its CORE_TEMP,
CORE_VBAT and CORE_VREF internal channels, and ADCBlock/ADCWiPy are ESP32 and Pycom
WiPy specific; this chip's ADC has one fixed input range and a fixed 10-bit resolution, so none
of them apply.
machine.IDLE, SLEEP, DEEPSLEEP, HARD_RESET, SOFT_RESET, DEEPSLEEP_RESET,
PIN_WAKE, RTC_WAKE, WLAN_WAKE and machine.SPI.CONTROLLER appear in the rp2
stub but not in the rp2 firmware's machine module, measured on MicroPython 1.21
on real RP2040 silicon: they are esp32-port names the stub carries over. This
layer matches the firmware, not the stub, so the only reset-cause constants are
PWRON_RESET (1) and WDT_RESET (3). machine.Pin.mode, Pin.pull,
Pin.drive, machine.I2C.deinit, machine.SoftI2C.deinit and
machine.ADC.read are the same
story: the stub carries them from ports that keep separate mode/pull/drive
accessors or a bus teardown method, while the rp2 firmware re-initialises a pin
through Pin.init(mode=..., pull=..., drive=...), defines none of them, and
reads its ADC only through ADC.read_u16().
machine.Pin's ALT_* function-select constants, DRIVE_0/DRIVE_1/DRIVE_2, drive,
ANALOG, PULL_HOLD, IRQ_HIGH_LEVEL and IRQ_LOW_LEVEL describe the RP2040's
per-pin function multiplexer, programmable drive-strength register and level-triggered IRQ
modes; this chip's GPIO pins are fixed-function with a fixed drive strength and only
edge-triggered external interrupts, so none of them apply. Pin.OPEN_DRAIN keeps its
upstream value (2) because the name exists upstream, but selecting the mode is refused on
this chip -- the AVR GPIO block has no open-drain output configuration.
machine.I2S, machine.RTC, machine.SDCard and machine.USBDevice are not implemented:
this chip has no I2S audio peripheral, no real-time-clock block, no SD/SDIO controller and no
USB device controller. machine.mem32 is not implemented either: this is an 8-bit architecture
with a 16-bit address bus, and mem8/mem16 already reach everything a mem32 window would.
machine.mem_backup and machine.bootloader are not implemented: there is no always-on backup
memory domain and no software-triggered bootloader entry point on this chip.
machine.UART's CTS, RTS, IDLE, INV_RX, INV_TX, IRQ_BREAK, IRQ_RX, IRQ_RXIDLE
and IRQ_TXIDLE are not implemented: this chip's USART has no hardware flow-control pins and
this HAL does not expose a break-detect or RX-idle-timeout interrupt source.
micropython.mem_info, qstr_info, stack_use, heap_lock, heap_unlock, opt_level,
kbd_intr, alloc_emergency_exception_buf, RingIO and Const_T describe MicroPython's
bytecode interpreter and its heap and interned-string table; PyMCU compiles ahead of time to
native code with no interpreter, heap or qstr table at runtime, so there is nothing for them to
report on. micropython.asm_thumb and micropython.asm_xtensa are inline assemblers for
architectures this compiler does not target.
time.gmtime, localtime, mktime, time and time_ns (and their utime aliases) need a
real-time clock to track wall-clock time across resets; this chip has no RTC peripheral, so
only the free-running ticks_ms()/ticks_us() counters are available.
MicroPython's own stub file marks PWM.init()'s freq, duty_u16, duty_ns and invert
keyword defaults as ... (implementation-defined); this layer's concrete 0 defaults are the
actual values applied when a keyword is omitted.
A transfer may be as long as the buffer, up to 65535 bytes. It used to be capped at 255
without saying so: the byte count travelled from machine.I2C.writeto through the HAL in
an 8-bit parameter, so a transfer of len & 0xFF bytes went out. 255 arrived whole, 256
sent nothing at all, 300 sent forty-four and the 513 bytes of an SSD1306 frame sent one.
Nothing was reported, on the bus or while compiling, because the count is a folded len()
rather than a literal and the compiler's narrowing refusal only inspects literals.
The write paths are checked on the wire, at 255, 256, 300 and 513 bytes for I2C and at
255, 256 and 300 for SPI, under both front ends
(tests/integration/fixtures/i2c-write-long and spi-write-long in the pymcu-avr
checkout).
The read paths carry the same count and were widened with the writes, but they are not verified: asserting a read longer than 255 bytes needs a slave script the emulator's recorder does not offer yet. Treat them as widened, not as measured.
machine.I2C.write and machine.I2C.readinto, the raw primitives used between an explicit
start() and stop(), still count in eight bits. Use writeto for anything longer than
255 bytes until that is fixed.
The CircuitPython layer never had this: its busio carries its own 16-bit loop instead of
sharing this HAL path.
machine.I2C.readfrom(addr, nbytes), machine.I2C.readfrom_mem(addr, memaddr, nbytes) and
machine.SPI.read(nbytes) (and their SoftI2C / SoftSPI twins) take upstream's
signatures and return nbytes bytes. There is no heap: the methods are @inline, so the
buffer they return lives in the caller's frame, and nbytes has to be a compile-time
constant, which is how drivers call them (i2c.readfrom_mem(addr, 0x75, 1)[0]). Two
differences remain: the result is a bytearray, where upstream's is an immutable bytes,
and a run-time nbytes is refused while compiling.
readfrom_mem used to be a PyMCU extension, readfrom_mem(addr, memaddr, buf, n), so the
upstream call did not compile. That form is gone; its caller-buffer equivalent is
readfrom_mem_into(addr, memaddr, buf).
machine.UART.read(nbytes) and the no-argument UART.readline() are refused while
compiling: upstream returns however many bytes arrived before the timeout, a length a
fixed-size buffer cannot carry. UART.readinto(buf[, nbytes]) is the buffer-based
equivalent, with upstream's timeouts: it waits timeout ms (default 0) for the first byte
and timeout_char ms (at least 13 bit times) for each one after, and returns how many
arrived, 0 where upstream answers None. The no-argument UART.read() still blocks for one
byte and returns it, a PyMCU form.
machine.UART.flush() waits until the data register is empty and then for one frame, so
it may wait one frame longer than needed. machine.UART.txdone() is refused while
compiling: the flag that says the shift register is idle (TXC) is only meaningful if every
write clears it first, which this UART does not do. UART.init() reprograms the rate and
frame; the read timeouts are the constructor's.