Delete very large SharePoint Online list or doclib
Install-Module -Name PnP.PowerShell -Scope CurrentUser
Connect-PnPOnline -Url "https://quantr.sharepoint.com/gitlab" -Interactive
Remove-PnPList -Identity "Commit" -Recycle -Force -LargeList
Get-PnPLargeListOperationStatus -Identity "60f2e783-aba5-46e8-9621-070ede81482b" -OperationId "2a93bded-42f7-4879-977a-20d631216bc4"

https://gemini.google.com/app/00a9d617585ad234

!!! This is not the perfect guide, during the compilation it got errors, ask gemini. https://share.gemini.google/bFyDzMtkYZVk

Step 1: Install Tools

Step 1: Install Vitis (it has Vivado)

Step 2: Install petalinux https://www.amd.com/en/products/software/adaptive-socs-and-fpgas/embedded-software/petalinux-sdk.html

chmod+x petalinux-v20XX.X-final-installer.run
./petalinux-v20XX.X-final-installer.run

Open Vivado and create project

Project name : petalinux_boot_from_flash_vivado

Choose: XC7Z020CLG400-2

petalinux-package --boot --fsbl images/linux/zynq_fsbl.elf --u-boot images/linux/u-boot.elf --force

Step 2: Build the image using petalinux

source /opt/petalinux/settings.sh
petalinux-create --type project --template zynq --name petalinux_boot_from_flash
cd petalinux_boot_from_flash
find .. -name "*.xsa"
cp ../petalinux_boot_from_flash_vivado/design_1_wrapper.xsa .
petalinux-config --get-hw-description=.

(Just save and exit)

petalinux-package --boot --fsbl images/linux/zynq_fsbl.elf --u-boot images/linux/u-boot.elf --force

now you got BOOT.BIN

~/xilinx/2026.1/Vitis/bin/program_flash -f images/linux/BOOT.BIN \
       -offset 0x0 \
       -fsbl images/linux/zynq_fsbl.elf \
       -flash_type qspi_single \
       -verify \
       -url tcp:localhost:3121

Add macros and the rest of a professional RISC-V assembler

The RISC-V front end already encodes instructions and a handful of data directives. It does not assemble the files you actually write. Those are GNU as: .macro / .endm\name, numbered local labels, .option.pushsectionr-test/fp.S is the concrete target.

This document tells you how to add that, in order. Do not expand macros inside the ANTLR grammar. The encoder already advances address in parser actions; a .macro body that is a lines rule will emit bytes at definition time, or fight the address, or both.

The one-line version

Parse once to collect directives, expand macros / conditionals / includes into plain source, then parse again to encode. New language features that invent text belong in a preprocessor. New language features that invent bytes (.align.globl.option) belong in the grammar and RISCVEncoder.

source .S  →  preprocessor  →  ANTLR parse + encode  →  bin / ELF

Dialect for everything new is GNU as. Keep the NASM %define / %include that already work so testbench/define.s does not break. Do not add a second full NASM %macro unless a later day needs it.

What already exists

Do not reinvent these. They are incomplete, not absent.

PieceWhereWhat it actually does
preProcessAssemblerLib.javaParse, then string-replace %include and %define
%definelexer DEFINEDefineListenerFlat name → value. No parameters. Redefine calls System.exit
%includelexer INCLUDEIncludeListenerInlines a file. Search path is hardcoded testbench/
%ifdef / %elif / %else / %endifgrammar ifdefParsed. Never evaluated
%times / timeslexer TIMESpreProcessEmpty if (content.contains("%times"))
.byte .half .word .dword .stringgrammar dot*Encoded. .string takes IDENTIFIER, not a quoted string
. IDENTIFIERgrammar sectionStores a name for the listing. No real section
labelsgrammar labelIDENTIFIER COLON only. No 1: / 1b / 1f
commentslexer LINE_COMMENT; only. GNU as uses #
listing-lRISCVEncoder.listingRecords the line the parser saw, not an expansion
ELFAssembler.java -f elfAlready writes via executablelibrary

DefineListener.map and DefineListener.lines are static. A second file in the same JVM inherits the first file's defines. Make them instance state when you touch that class.

The grammar rule named macro is not a macro. It is a bucket for define, ifdef, include, and data:

macro : define | ifdef | include | dotbyte | dothalf | dotword | dotdword | dotstring ;

Leave that name alone until the preprocessor owns define/ifdef/include. Then the rule can shrink to data directives.

Why the preprocessor, not the grammar

RISCVAssemblerParser members hold address and every instruction does address+=encoder.encodeType...(...). A .macro ADD rd, rs whose body is parsed as lines will encode add while you are still defining ADD. The invocation then has nothing to expand, or encodes a second time.

preProcess already does the right shape: walk the text, produce new text, then the real assemble() encodes. Grow that into a package hk.quantr.assembler.riscv.preprocess rather than piling more static maps into AssemblerLib.

Assembler.main must call the preprocessor before the encode parse. Today preProcess is used from tests (TestMacroTestMissingMacro) and is easy to skip from the CLI path. Grep preProcess( and make every assemble entry go through it.

Files you will touch

src/main/java/hk/quantr/assembler/antlr/RISCVAssemblerLexer.g4
src/main/java/hk/quantr/assembler/antlr/RISCVAssemblerParser.g4
src/main/java/hk/quantr/assembler/AssemblerLib.java
src/main/java/hk/quantr/assembler/Assembler.java
src/main/java/hk/quantr/assembler/riscv/listener/DefineListener.java
src/main/java/hk/quantr/assembler/riscv/listener/IncludeListener.java
src/main/java/hk/quantr/assembler/riscv/RISCVEncoder.java
src/main/java/hk/quantr/assembler/riscv/preprocess/   (new)
src/test/java/hk/quantr/assembler/riscv/TestGasMacro.java  (new)

After any .g4 change: mvn -DskipTests compile so ANTLR regenerates into target/generated-sources/antlr4.

Phase 1: real macros

This is the feature the request named. Do it first and stop to test.

Tokens

In RISCVAssemblerLexer.g4, next to DEFINE / INCLUDE:

DOTMACRO    :   '.macro';
DOTENDM     :   '.endm';
DOTEXITM    :   '.exitm';

Do not add a parser rule that treats the body as lines. The preprocessor reads these as lines of text.

Data

A definition is a name, a list of formal parameters, and the raw body (lines between .macro and .endm, not encoded). An invocation is a name used as an opcode with comma-separated arguments.

GNU as substitution:

In the bodyBecomes
\formalthe matching argument
\\@a counter that increments every invocation (unique local labels)
\()concatenator, so \a\()b is arg a then the letter b

GAS also allows .macro NAME arg1=default. Defaults can wait until the required-argument form works.

Algorithm

  1. Walk the source line by line. # and ; comments strip for this walk, but keep the original line text for error reporting.
  2. On .macro NAME [formals...], slurp until .endm. Nested .macro inside a body is stored, not executed. Missing .endm is an error that names the opening line.
  3. On a line whose first identifier is a defined macro, split arguments on commas that are not inside (...) or quotes, bind formals, substitute, splice the expansion in place of the invocation, and re-scan from there so macros can call macros.
  4. Recursion depth: cap at something like 100. The error must name the invocation site and the definition site.
  5. Unknown arity: error, do not encode a truncated body.

Invocations look like instructions (SECTION nameT fldLDD fs0, d_one). The encode parse must not see those names. After expansion, SECTION is gone and the body (lacall, ...) remains.

What not to put in the grammar

Do not add NAME args as a generic instruction alternative. That would steal real opcodes (addld) if someone names a macro after one. Expansion happens first; the parser only ever sees real instructions.

First golden test

r-test/fp.S macros, smallest useful subset:

	.macro	E
	la	a3, 7b
	call	test_end
	.endm

	.macro	LDD freg, sym
	la	a0, \sym
	c.fld	\freg, 0(a0)
	.endm

A file that does LDD fs0, scratch then E must expand to la / c.fld / la / call. Compare bytes to riscv64-elf-as (phase 1 can ignore .option and .pushsection by not using those lines yet).

TestGasMacro outline:

String src = Files.readString(Path.of("src/test/resources/macro_ldd.s"));
String expanded = Preprocessor.expand(src, "rv64");
byte[] ours = assembleRv64(expanded);
byte[] gas = gasAssemble(src);   // riscv64-elf-as -march=rv64imafdc
assertArrayEquals(gas, ours);

gasAssemble writes a temp .s, runs riscv64-elf-as -o t.o, then riscv64-elf-objcopy -O binary t.o t.bin (or read .text from the ELF). Same idea as FullTest versus gas.

Phase 1 is done when that test passes and testbench/define.s still works.

Phase 2: conditionals and repeats

%ifdef is already in the grammar and does nothing. Evaluating it in the parser would still encode the false branch, because both lines alternatives are walked. Evaluate in the preprocessor, then delete the ifdef parser rule or leave it as a no-op that never fires on expanded text.

Add GAS forms. These are what fp.S does not use yet but every real tree has:

.if  expr
.ifdef name
.ifndef name
.else
.endif
.rept N
.endr

.if uses the same expression evaluator as immediates (CalculatorLibrary.cal). A name in .ifdef is defined if it is in the %define / .equ table or is a .macro.

.rept N splices the body N times, then re-scans. That is also the correct implementation of times / %times (the empty branch in preProcess).

.irp reg, t0, t1, t2 can wait until .rept works. It is the same loop with a formal rebound each iteration.

False branches must not define macros and must not invoke them. Skip lines until the matching .else / .endif, tracking nest depth. A dangling .endif is an error.

Phase 3: symbols and local labels

Without this, expanded fp.S still will not assemble.

.equ / .set

Same table as %define.set may redefine; %define today forbids it and exits. Pick one policy and document it: .set overwrites, %define of an existing name is an error (keep today's behaviour).

Lexer: DOTEQU : '.equ'; DOTSET : '.set';

The encode parse needs these names in immediates. Either the preprocessor substitutes them (like %define already does with replaceAll("\\b"+name+"\\b")) or CalculatorLibrary consults the table. Substitution is simpler and matches the current %define path.

Numbered local labels

GNU as: 1: through 19: (you only need 09 to start), referenced as 1b (nearest backward) and 1f (nearest forward). fp.S uses 7: / 7b and 9: / 9b.

This cannot be only a preprocessor rewrite of the current file, because 1f depends on a label that appears later. Do it in the encoder:

  1. Lexer: allow label to be [0-9]+ COLON, and immediates / jump targets to be [0-9]+ [bf].
  2. First pass, or a collected list: each N: at address A is pushed on a per-digit list.
  3. When encoding jal / beq / la that uses 7b, take the last 7: whose address is <= current address; 7f takes the next one after.

If you stay strictly one-pass, 7f is unknown when you see it. The encoder already has labels on the parser (ArrayList<Label>). Either two-pass (walk once for labels, once to encode) or record a fixup and patch the immediate when the forward label is defined. Two-pass is less clever and matches how gas works. The current address+= one-pass is why forward regular labels are already shaky; this is the moment to make a label pass explicit if you have to fight it.

# comments

LINE_COMMENT : [;#] ~[\r\n]* ;

GAS also treats /* */ as comments. Not required for fp.S# is.

Phase 4: sections and options

Needed for a real object, and for fp.S after macros expand.

Sections

Replace the catch-all

section : DOT sectionName=IDENTIFIER IDENTIFIER? ;

with explicit directives so .macro / .equ / .option are not eaten as section names (today . plus an identifier is a section).

.text
.data
.rodata
.bss
.align  N
.globl  name
.asciz  "string"
.ascii  "string"
.pushsection name
.popsection

.byte already exists. Add .asciz (NUL-terminated) distinct from .string if .string stays identifier-only; or teach .string to take DOUBLE_QUOTATION ... DOUBLE_QUOTATIONfp.S uses .asciz.

RISCVEncoder must keep a current section and a stack for .pushsection / .popsection. Bytes go into that section's buffer, not one flat out. ELF output (-f elf) already exists; point it at those buffers instead of a single blob. bin output can concatenate .text then .data the way a raw image expects, or refuse and require -f elf. Pick one and test it.

.align N on RISC-V gas is power-of-two (.align 3 means 8 bytes). Pad with zeros or nop/c.nop in .text. Wrong interpretation here will fail every gas comparison.

.option

.option rvc
.option norvc
.option push
.option pop

LDD in fp.S wraps c.fld in .option push / rvc / pop. Without this, either you always accept compressed (current behaviour) or you reject c.fld when someone writes .option norvc. Store a stack of flags on the encoder. encodeType* for compressed opcodes checks rvcEnabled.

Phase 5: professional tooling

Do this after the language works. None of it changes what bytes mean.

  • Include path. preProcessIncludeFile opens "testbench/" + to. Resolve against the including file's directory, then each -I from Assembler.main. Add the CLI option next to -a / -o.
  • Listing of expansions. -l already writes listing. After preprocess, each encoded line should carry the invocation that produced it, so a failure in an expanded T fld names T and the .macro T line.
  • Errors. Invocation site plus definition site, for macros, includes, and missing .endm. Stop calling System.exit from DefineListener / preProcess; throw or record on MessageHandler and let main set the exit code. Tests cannot survive an exit.
  • Instance state. DefineListener.map and friends must not be static.

How to test, every phase

Always compare to GNU as. That is the project's existing contract (FullTest / gas vs quantr).

# gas
riscv64-elf-as -march=rv64imafdc -o gas.o t.s
riscv64-elf-objcopy -O binary --only-section=.text gas.o gas.bin

# ours, after you wire preProcess into main
java -jar target/assembler-*-jar-with-dependencies.jar -a rv64 -f bin -o ours.bin t.s

cmp gas.bin ours.bin

Keep a table of fixtures under src/test/resources/riscv/:

FilePhaseWhat it proves
define_still_works.s1%define + addi unchanged
macro_ldd.s1.macro args, \formal
macro_nested.s1macro calls macro
macro_count.s1\\@ unique labels
ifdef_false.s2false branch emits nothing
rept_3.s2body appears three times
local_label.s31: ... j 1b
option_rvc.s4c.fld only with .option rvc
pushsection.s4string lands in .rodata, code in .text

testbench/define.s and testbench/macro.s stay green.

Acceptance

Phase 1–4 together: this jar assembles r-test/fp.S (or a copy with the # comments and .equ values it already has) and the .text bytes match riscv64-elf-as -march=rv64imafdc. That file uses every phase: macros with arguments, # comments, 7: / 7b.option push/rvc/pop.pushsection.asciz.

Until that works, do not start ia32 macros, a C preprocessor, or a relocating linker. Those are different jobs.

Out of scope

  • Rewriting the encoder into a full relocating linker (relocs, -shared)
  • ia32 / NASM %macro / %endmacro
  • Running cpp for #include / #ifdef / #define (GAS # is a comment, not cpp, unless you pass -x assembler-with-cpp)
  • Changing instruction encodings or the disassembler

Add this to your .bashrc

export GDK_SCALE=2 GDK_DPI_SCALE=0.5
export _JAVA_OPTIONS="-Dsun.java2d.uiScale=2.5"

!!! This is not the perfect guide, during the compilation it got errors, ask gemini. https://share.gemini.google/bFyDzMtkYZVk

I am using this board

Step 1: Install Vitis (it has Vivado)

Step 2: Install petalinux https://www.amd.com/en/products/software/adaptive-socs-and-fpgas/embedded-software/petalinux-sdk.html

chmod +x petalinux-v20XX.X-final-installer.run
./petalinux-v20XX.X-final-installer.run

Step 3: Add the board files to Vivado

git clone https://github.com/karolzmijewski/z7-nano-7020.git
mkdir /home/peter/xilinx/2026.1/data/boards/board_files
cp -r z7-nano-7020/board_files/z7-nano-7020 /home/peter/xilinx/2026.1/data/boards/board_files/

Step 4: Build a minimal hardware design in Vivado

  1. vivado → Create New Project → RTL Project, no sources
  2. Choose Boards tab → select Z7-Nano-7020 (now visible thanks to step 2)
  3. Create Block Design → add ZYNQ7 Processing System → click Run Block Automation (it will apply the board preset automatically: DDR3, UART, SD, ETH, USB)
  4. Validate the design (F6), no errors
  5. Create HDL wrapper → right click wrapper → Generate Bitstream
  6. File → Export → Export Hardware → check Include bitstream → produces a .xsa file (e.g. z7nano_wrapper.xsa)

Step 5: Create the PetaLinux project

source home/peter/xilinx/2026.1/Model_Composer/settings64.sh
source /opt/petalinux/settings.sh
petalinux-create -t project --template zynq -n z7nano-linux
cd z7nano-linux
petalinux-config --get-hw-description=/path/to/xsa/dir

In the config menu that opens:

  • Subsystem AUTO Hardware Settings → confirm/adjust serial console (should be ps7_uart_1 or _0 matching your design), Ethernet, SD
  • Image Packaging Configuration → Root filesystem type → set to EXT4 (SD/eMMC/USB) (so rootfs lives directly on the SD card, not inside an initramfs)
  • Boot Image Settings → FSBL and u-boot should default correctly for Zynq-7000
petalinux-config -c kernel
petalinux-config -c rootfs 

Step 6: Build

petalinux-build

This produces (under images/linux/): zynq_fsbl.elfu-boot.elfimage.ub (kernel+devicetree+ramdisk fitImage), system.dtbrootfs.tar.gz.

Then package the boot binary:

petalinux-package --boot --fsbl images/linux/zynq_fsbl.elf \
                   --fpga images/linux/system.bit \
                   --u-boot --force

petalinux-package --boot --fsbl images/linux/zynq_fsbl.elf --u-boot images/linux/u-boot.elf --force

This creates images/linux/BOOT.BIN.

Step 7: Prepare the microSD card

Partition it with two partitions (use fdisk/gparted):

PartitionSizeTypeContents
1~500MBFAT32, boot flagBOOT.BINimage.ubboot.scr (if generated)
2remainderext4extracted rootfs
sudo mkfs.vfat -F 32 -n BOOT /dev/sdX1
sudo mkfs.ext4 -L rootfs /dev/sdX2
sudo mount /dev/sdX1 /mnt/boot
sudo cp images/linux/BOOT.BIN images/linux/image.ub /mnt/boot/
sudo umount /mnt/boot
sudo mount /dev/sdX2 /mnt/root
sudo tar xzf images/linux/rootfs.tar.gz -C /mnt/root
sudo umount /mnt/root

Step 8: Set the boot mode jumper

On the Z7-Nano, boot mode is set by jumper J1 ("MODE" pins) — set it to the SD boot position (JTAG/QSPI/SD options are silkscreened near J1; check the reference manual's Boot Config diagram or the schematic if the silkscreen is unclear — standard Zynq-7000 SD boot mode pins are MIO[6:2] = 1 0 1 0 1).
Step 9: Boot it

  1. Insert the microSD card
  2. Connect the USB-UART port to your PC (/dev/ttyUSB0, appears as CH340 device)
  3. Open a serial terminal: screen /dev/ttyUSB0 115200 (or minicom -D /dev/ttyUSB0 -b 115200)
  4. Power the board via USB
  5. You should see FSBL → U-Boot → kernel boot messages, ending in a login prompt (default PetaLinux root/root or root/petalinux depending on version config)

Troubleshooting notes

  • DDR/FSBL hangs at boot: usually a MIG/PS7 DDR config mismatch — re-run Block Automation in Vivado rather than hand-editing PS7 DDR settings, since the board preset already has correct MT41K256M16 timings.
  • No Ethernet: the RTL8211F PHY sometimes needs a reset GPIO toggle in the device tree (phy-reset-gpio) — check karolzmijewski/z7-nano-7020 examples for the exact PHY reset pin if this happens.
  • U-Boot doesn't find image.ub: confirm the FAT32 partition has the boot flag set and file names match what petalinux-package produced.
  • Faster iteration: once this works, you can skip re-running Vivado each time and just re-run petalinux-build + petalinux-package for software-only changes (keep the same .xsa unless you change PL hardware).

Alternative: PYNQ instead of plain PetaLinux

Since the board explicitly advertises a microSD slot "for PYNQ," if your goal is Python/Jupyter-based FPGA development rather than a bare Linux console, you can follow the same Vivado XSA export above but instead build via the PYNQ SD card image build flow, which layers Jupyter + the PYNQ Python overlay framework on top of a PetaLinux-built image. It's more work (bigger BSP customization, xilinx-pynq recipe) but gives you a full Jupyter notebook environment on the board out of the box.

Let me know which route you want (plain embedded Linux console vs. PYNQ/Jupyter), and whether you'd like help writing the actual Vivado TCL block-design script or PetaLinux device-tree overlay for specific peripherals (Ethernet, HDMI, GPIO) — I can generate those files for you.

1. Install Git LFS

  • macOS (via Homebrew): brew install git-lfs
  • Ubuntu / Debian: sudo apt install git-lfs
  • Windows (via Chocolatey or standalone installer):PowerShellchoco install git-lfs

2. Initialize Git LFS

Run this command once on your computer to hook it into Git:

Bash

git lfs install

Step 4: Track and Push Large Files in a Repository

To use LFS inside a repository, point Git to the specific file types you want to manage.

  1. Clone your repository and navigate into it: git clone [email protected]:group/my-project.git cd my-project
  2. Tell Git LFS which file extensions or patterns to track (e.g., .psd, .iso, .zip, .mp4): git lfs track "*.iso" This command automatically creates or updates a .gitattributes file.
  3. Important: Ensure the .gitattributes file is committed to your repository, otherwise collaborators will run into errors cloning LFS files:
    git add .gitattributes
    git commit -m "Configure Git LFS tracking"
  4. Add, commit, and push your large files as you normally would with Git:
    cp ~/Downloads/large-file.iso ./
    git add large-file.iso
    git commit -m "Add large file via Git LFS"
    git push origin main

You will see output indicating that Git LFS is uploading the binary blobs separately from the standard Git commit metadata.

    "http.proxy": "http://3.1.240.71:8888",
    "http.proxySupport": "override",
    "http.noProxy": [
        "localhost",
        "127.0.0.1",
        "192.168.1.88",
        "192.168.1.88:1234",
        "61.244.87.154",
        "github.com",
        "gitlab.quantr.hk",
        "gitlab.hkprog.org"
    ],
    "chat.sessionSync.enabled": true,
    "cursor.general.disableHttp2": true

Step 1: Qemu. First test the default qemu, then modify in step 3

git clone [email protected]:qemu/qemu.git

export PATH="$(brew --prefix bison)/bin:$PATH"
export PKG_CONFIG_PATH="$(brew --prefix glib)/lib/pkgconfig:$PKG_CONFIG_PATH"

# ./configure --extra-cflags="-I/opt/homebrew/opt/libiconv/include -I/usr/local/include" --extra-ldflags="-L/opt/homebrew/opt/libiconv/lib -L/usr/local/lib" --target-list=riscv64-softmmu --enable-plugins

./configure --extra-cflags="-I$(brew --prefix libiconv)/include -I/usr/local/include" --extra-ldflags="-L$(brew --prefix libiconv)/lib -L/usr/local/lib" --target-list=riscv64-softmmu --enable-plugins

make -j
sudo make install

Step 2: xv6-riscv

brew install riscv64-elf-gcc riscv64-elf-binutils riscv64-elf-gdb
git clone [email protected]:mit-pdos/xv6-riscv.git
make -j
make qemu

If you meet this, comment the variable out

Step 3: modify the qemu

follow this https://www.quantr.foundation/project/?project=QEMU%20Log%20Panel

ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/github
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/github
cat ~/.ssh/github.pub

https://github.com/settings/keys

Add key permanently

vi ~/.ssh/config
Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/github
  AddKeysToAgent yes

Step 1:

Check your Tools menu and make sure your settings look exactly like this:

  • Board: Generic STM32F4 series
  • Board Part Number: BlackPill F411CE (Do not pick the generic F411 options)
  • U(S)ART support: Enabled (generic 'Serial')
  • USB support: CDC (generic 'Serial' supersede U(S)ART)
//PA10 (RX) and PA9 (TX)

void setup() {
  // Initialize USB CDC
  Serial.begin(9600); 
  
  // Initialize Hardware UART
  Serial1.begin(9600); 
}

void loop() {
  // Read from USB and send to UART
  if (Serial.available()) {
    Serial1.write(Serial.read());
  }
  
  // Read from UART and send to USB
  if (Serial1.available()) {
    Serial.write(Serial1.read());
  }
}

#define F_CPU 16000000UL  // 1. Define CPU Frequency (16MHz is common for 328PB)
#include <avr/io.h>
#include <util/delay.h>   // 2. Include the delay library

int main(void) {
    // 3. Set PD7 as an output
    // DDRD is the Data Direction Register for Port D
    DDRD |= (1 << DDD7); 

    while (1) {
        // 4. Toggle PD7 using the XOR operator
        PORTD ^= (1 << PORTD7);
        
        // 5. Wait for 500 milliseconds
        _delay_ms(500);
    }
}

To burn the hex to avr in mac using command line, use this Makefile

export PATH="/Applications/microchip/mplabx/v6.30/mplab_platform/mplab_ipe/:$PATH"
MODEL=atmega328p
MODEL_AVRDUDE=m328pb
# MODEL_ISP=usbasp-clone
MODEL_ISP=avrisp2 # mkII

# For Microchip IPECMD, the device name usually needs to match the exact chip
MODEL_IPE=ATmega328PB
# IPECMD Tool Configuration
# -TPPK5 tells IPE to use the PICkit 5
TOOL_IPE=-TPPK5

all: SSD1306.o TWI.o main.hex

SSD1306.o: ssd1306/SSD1306.c
	avr-gcc -mmcu=$(MODEL)  -Wall -Os -c $? -o $@

TWI.o: ssd1306/TWI.c
	avr-gcc -mmcu=$(MODEL)  -Wall -Os -c $? -o $@

main.hex: main.c
	avr-gcc -mmcu=$(MODEL)  -Wall -Os -o main.elf SSD1306.o TWI.o main.c
	avr-objcopy -j .text -j .data -O ihex main.elf main.hex
	avr-size --format=avr --mcu=$(MODEL) main.elf

upload:
	avrdude -c $(MODEL_ISP) -p $(MODEL_AVRDUDE) -U flash:w:main.hex

upload_pickit5:
	ipecmd.sh $(TOOL_IPE) -P$(MODEL_IPE) -F"$(CURDIR)/main.hex" -M

readfuse:
	avrdude -c $(MODEL_ISP) -p $(MODEL_AVRDUDE) -U hfuse:r:-:h -U lfuse:r:-:h

writefuse:
	avrdude -c $(MODEL_ISP) -p $(MODEL_AVRDUDE) -U lfuse:w:0x62:m -U hfuse:w:0xD9:m -U efuse:w:0xFF:m -U lock:w:0xFF:m

clean:
	-rm *.o
	-rm main.hex
	-rm main.elf

I bought 10 ATF22V10C from here and T48 programmer here, got 1 broken. First, here is the simple and gate program, save it to and.pld

GAL22V10
AND_Gate

Clock A  B  NC NC NC NC NC NC NC NC GND
NC    NC NC NC NC NC NC NC NC NC Y  VCC


Y = A * B			; AND gate: Y is high only when both A and B are high


DESCRIPTION
Simple 2-input AND gate example using a GAL22V10.
Inputs:  A (pin 2), B (pin 3)
Output:  Y (pin 23) - combinatorial output

Build galasm and minipro. Then compile and burn it by:

galasm and.pld
minipro -p ATF22V10C -w and.jed

To read back the jed from chip, you can

minipro -p ATF22V10C -r output.jed

You can just compare the output.jed to your original and.jed, because and.jed is compiled by galasm and shortformed. I have a python to expand the jed, so the addresses in both jed files will be aligned.

See the address on left hand side then you see it is shortformed

import re
import sys

def expand_jedec_32bits(input_text):
    # 1. Determine default value from *F flag
    default_val = '0' if '*F0' in input_text else '1'
    
    qf_match = re.search(r'\*QF(\d+)', input_text)
    if not qf_match:
        raise ValueError("Could not find fuse count (*QF) field in JEDEC file.")
    total_fuses = int(qf_match.group(1))
    
    # 2. Initialize the entire fuse array with the default value
    fuse_array = [default_val] * total_fuses
    
    # 3. Parse and fill explicit allocation fields (*LXXXX)
    l_fields = re.findall(r'\*L(\d+)\s+([01\s]+)', input_text)
    
    for start_index_str, bit_string in l_fields:
        start_index = int(start_index_str)
        bits = bit_string.replace(" ", "").replace("\n", "").replace("\r", "")
        
        for i, bit in enumerate(bits):
            if start_index + i < total_fuses:
                fuse_array[start_index + i] = bit

    # 4. Calculate Fuse Checksum (Sum of all 8-bit fuse bytes)
    fuse_checksum = 0
    for i in range(0, total_fuses, 8):
        byte_bits = "".join(fuse_array[i:i+8])
        if len(byte_bits) < 8:
            byte_bits = byte_bits.ljust(8, '0')
        # Standard JEDEC checksum mirrors the bit order of each byte
        byte_val = int(byte_bits[::-1], 2)
        fuse_checksum = (fuse_checksum + byte_val) & 0xFFFF

    # 5. Construct the Longform Output Body (Row width = 32 bits)
    output_lines = []
    
    # Grab everything before the QF tag to keep original headers/comments
    header_end_idx = input_text.find('*QF')
    output_lines.append(input_text[:header_end_idx].strip())
    output_lines.append(f"*QF{total_fuses}*")
    
    row_size = 32  # 32 bits per row configuration
    for addr in range(0, total_fuses, row_size):
        chunk = fuse_array[addr:addr+row_size]
        chunk_str = "".join(chunk)
        
        # Append row format: *L<address> <bits>*
        # Keeping a space here as it is standard formatting for readability
        output_lines.append(f"*L{addr:05d} {chunk_str}")

    # Add the generated fuse checksum
    output_lines.append(f"*C{fuse_checksum:04X}*")
    
    # End of text transmission block
    output_body = "\n".join(output_lines) + "\n\x03"
    
    # 6. Calculate File Checksum (ASCII sum from STX to ETX)
    file_checksum = sum(ord(c) for c in output_body) + 0x02  # Include STX (0x02)
    file_checksum &= 0xFFFF
    
    final_jedec = f"\x02\n{output_body}{file_checksum:04x}"
    return final_jedec

# --- Execution ---
if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: expand_jedec.py <input.jed> [output.jed]")
        sys.exit(1)

    input_file = sys.argv[1]
    output_file = sys.argv[2] if len(sys.argv) > 2 else input_file.rsplit(".", 1)[0] + "_expanded.jed"

    try:
        with open(input_file, "r", encoding="ascii") as f:
            shortform_data = f.read()

        longform_jedec = expand_jedec_32bits(shortform_data)
        with open(output_file, "w", encoding="ascii") as f:
            f.write(longform_jedec)
        print(f"Success! Generated '{output_file}' with 32-bit width arrays.")
    except FileNotFoundError:
        print(f"Error: Input file '{input_file}' not found.")
        sys.exit(1)
    except Exception as e:
        print(f"Error expanding JEDEC map: {e}")
        sys.exit(1)

run "python expand_jedec.py and.jed", then you got and_expand.jed, then you can diff and_expand.jed and and.jed to provide they are the same, so the program you burn to ATF22V10C are same as what you want (and.jed)

Disable cursor capturing my project's information to prevent leaking. Open settings.json and set

    "telemetry.enableTelemetry": false,
    "telemetry.telemetryLevel": "off"

If you build pulseview in mac, you got "fatal error: 'glib.h' file not found". Do these

cd /opt/homebrew/lib/pkgconfig   # or /usr/local/lib/pkgconfig if on Intel Mac
ln -s glibmm-2.68.pc glibmm-2.4.pc

cd /Users/peter/workspace/pulseview
rm -rf CMakeCache.txt CMakeFiles/
make clean   # or just rm -rf the build artefacts if needed
export PKG_CONFIG_PATH="/usr/local/opt/glib/lib/pkgconfig:/usr/local/opt/qt@6/lib/pkgconfig:$PKG_CONFIG_PATH"
cmake .
make -j

How to Run

mpremote exec "import flashRead_w25; flashRead_w25.dump_flash(0, 1280)"
mpremote exec "import flashWrite_w25; flashWrite_w25.write('w 2 0x23 3 0x45')"

Write

import machine
import time
import ssd1306
from machine import SPI, Pin

# W25Q128 SPI Flash Configuration
# W25Q128 has 16MB (16777216 bytes) = 128 Mbit
# Page size: 256 bytes
# Sector size: 4KB
# Block size: 64KB

# W25Q128 Commands
CMD_WRITE_ENABLE = 0x06
CMD_WRITE_DISABLE = 0x04
CMD_READ_STATUS = 0x05
CMD_READ_STATUS2 = 0x35
CMD_READ_STATUS3 = 0x15
CMD_WRITE_STATUS = 0x01
CMD_READ_DATA = 0x03
CMD_PAGE_PROGRAM = 0x02
CMD_SECTOR_ERASE = 0x20
CMD_BLOCK_ERASE_32K = 0x52
CMD_BLOCK_ERASE_64K = 0xD8
CMD_CHIP_ERASE = 0xC7
CMD_READ_ID = 0x9F
CMD_POWER_DOWN = 0xB9
CMD_RELEASE_POWER_DOWN = 0xAB
CMD_RESET_ENABLE = 0x66
CMD_RESET_MEMORY = 0x99

# SPI Configuration
spi = None
cs = None

def init_spi():
    global spi, cs
    # Initialize SPI bus (SPI1) for WeAct BlackPill
    # SCK=PA5, MISO=PA6, MOSI=PA7
    try:
        spi = machine.SPI(
            1,
            baudrate=1000000,
            polarity=0,
            phase=0,
            bits=8,
            firstbit=machine.SPI.MSB,
            sck=machine.Pin('A5'),
            mosi=machine.Pin('A7'),
            miso=machine.Pin('A6'),
        )
    except (ValueError, TypeError):
        try:
            spi = machine.SPI(1, baudrate=1000000, polarity=0, phase=0)
        except Exception:
            spi = machine.SoftSPI(
                baudrate=500000,
                polarity=0,
                phase=0,
                sck=machine.Pin('A5'),
                mosi=machine.Pin('A7'),
                miso=machine.Pin('A6'),
            )

    # CS pin (adjust based on your wiring)
    cs = machine.Pin('A4', machine.Pin.OUT, value=1)
    time.sleep_ms(1)

    flash_wake()
    flash_reset()

    disable_protection()
    
    # Check device ID
    device_id = read_device_id()
    if device_id == 0x000000:
        time.sleep_ms(5)
        device_id = read_device_id()
    print(f"W25Q128 Device ID: {device_id:06X}")
    if device_id == 0xEF4018:
        print("W25Q128 detected successfully")
    else:
        print(f"Warning: Unexpected device ID: {device_id:06X} (expected 0xEF4018)")

def read_device_id():
    """Read W25Q128 manufacturer and device ID"""
    cs.value(0)
    spi.write(bytes([CMD_READ_ID]))
    id_data = spi.read(3)
    cs.value(1)
    return (id_data[0] << 16) | (id_data[1] << 8) | id_data[2]

def flash_wake():
    """Release from power-down (safe to call even if not asleep)"""
    cs.value(0)
    spi.write(bytes([CMD_RELEASE_POWER_DOWN]))
    cs.value(1)
    time.sleep_ms(1)

def flash_reset():
    """Reset the flash (W25Q series supports 0x66/0x99)"""
    cs.value(0)
    spi.write(bytes([CMD_RESET_ENABLE]))
    cs.value(1)
    time.sleep_us(50)
    cs.value(0)
    spi.write(bytes([CMD_RESET_MEMORY]))
    cs.value(1)
    time.sleep_ms(1)

def read_status():
    """Read status register"""
    cs.value(0)
    spi.write(bytes([CMD_READ_STATUS]))
    status = spi.read(1)[0]
    cs.value(1)
    return status

def read_status2():
    """Read status register-2"""
    cs.value(0)
    spi.write(bytes([CMD_READ_STATUS2]))
    status = spi.read(1)[0]
    cs.value(1)
    return status

def read_status3():
    """Read status register-3"""
    cs.value(0)
    spi.write(bytes([CMD_READ_STATUS3]))
    status = spi.read(1)[0]
    cs.value(1)
    return status

def write_status(sr1, sr2):
    """Write status register-1 and -2"""
    wait_busy()
    write_enable()
    cs.value(0)
    spi.write(bytes([CMD_WRITE_STATUS, sr1 & 0xFF, sr2 & 0xFF]))
    cs.value(1)
    wait_busy()

def disable_protection():
    """Clear block protection bits and SRP"""
    sr1 = read_status()
    sr2 = read_status2()
    sr3 = read_status3()
    if (sr1 & 0x1C) or (sr1 & 0x80):
        print(f"Status before: SR1={sr1:02X} SR2={sr2:02X} SR3={sr3:02X}")
        write_status(sr1 & ~0x9C, sr2 & ~0x40)
        sr1 = read_status()
        sr2 = read_status2()
        sr3 = read_status3()
        print(f"Status after:  SR1={sr1:02X} SR2={sr2:02X} SR3={sr3:02X}")

def wait_busy():
    """Wait until write operation completes"""
    while read_status() & 0x01:
        time.sleep_us(10)

def write_enable():
    """Enable write operations"""
    cs.value(0)
    spi.write(bytes([CMD_WRITE_ENABLE]))
    cs.value(1)

def write_disable():
    """Disable write operations"""
    cs.value(0)
    spi.write(bytes([CMD_WRITE_DISABLE]))
    cs.value(1)

def read_byte(addr):
    """Read a single byte from address"""
    cs.value(0)
    spi.write(bytes([CMD_READ_DATA, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF]))
    data = spi.read(1)[0]
    cs.value(1)
    return data

def read_bytes(addr, length):
    """Read multiple bytes from address"""
    cs.value(0)
    spi.write(bytes([CMD_READ_DATA, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF]))
    data = spi.read(length)
    cs.value(1)
    return data

def write_page(addr, data):
    """Write up to 256 bytes (one page). Address must be page-aligned."""
    if len(data) > 256:
        raise ValueError("Page write data must be <= 256 bytes")
    
    wait_busy()
    write_enable()

    # Ensure write-enable latch is set (bit 1)
    if (read_status() & 0x02) == 0:
        write_enable()
        if (read_status() & 0x02) == 0:
            raise RuntimeError("Write enable latch not set. Check /WP pin.")
    
    cs.value(0)
    spi.write(bytes([CMD_PAGE_PROGRAM, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF]))
    spi.write(data)
    cs.value(1)
    
    wait_busy()

def write_byte(addr, value):
    """Write a single byte to address"""
    write_page(addr, bytes([value & 0xFF]))

def sector_erase(addr):
    """Erase a 4KB sector (sector address must be sector-aligned)"""
    wait_busy()
    write_enable()

    if (read_status() & 0x02) == 0:
        write_enable()
        if (read_status() & 0x02) == 0:
            raise RuntimeError("Write enable latch not set. Check /WP pin.")
    
    cs.value(0)
    spi.write(bytes([CMD_SECTOR_ERASE, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF]))
    cs.value(1)
    
    wait_busy()

def block_erase_64k(addr):
    """Erase a 64KB block (address must be block-aligned)"""
    wait_busy()
    write_enable()
    
    cs.value(0)
    spi.write(bytes([CMD_BLOCK_ERASE_64K, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF]))
    cs.value(1)
    
    wait_busy()

def chip_erase():
    """Erase entire chip (takes several seconds)"""
    wait_busy()
    write_enable()
    
    cs.value(0)
    spi.write(bytes([CMD_CHIP_ERASE]))
    cs.value(1)
    
    print("Chip erase started (this may take 10-30 seconds)...")
    wait_busy()
    print("Chip erase complete")

def write_00_to_ff():
    init_spi()
    
    # i2c_display = machine.I2C(1)
    # display = ssd1306.SSD1306_I2C(128, 64, i2c_display)
    # display.fill(0)
    # display.text("W25Q128 Writer", 5, 5, 1)
    # display.show()

    # Erase first 64KB (16 sectors) before writing
    # for sector in range(0, 16):
    #     sector_addr = sector * 0x1000
    #     print(f"E {sector_addr:06X}")
    #     sector_erase(sector_addr)

    # Write first 64KB for testing, page-by-page
    for page_addr in range(0, 65536, 256):
        if page_addr % 1000 == 0:
            print(f"W {page_addr:06X}")
            # display.fill(0)
            # display.text("W25Q128 Writer", 5, 5, 1)
            # display.text(f"W {page_addr:06X}", 5, 30, 1)
            # display.show()

        page = bytes([(page_addr + i) & 0xFF for i in range(256)])
        write_page(page_addr, page)

    # display.fill(0)
    # display.text("W25Q128 Writer", 5, 5, 1)
    # display.text(f"Write Complete", 5, 30, 1)
    # display.show()


def write(str):
    init_spi()
    
    # Parse the input string into address-value pairs and write each value
    str = str[2:]  # Remove "w " prefix
    tokens = str.strip().split()
    if len(tokens) % 2 != 0:
        raise ValueError("Input string must contain pairs of <addr> <value>")
    
    # i2c_display = machine.I2C(1)
    # display = ssd1306.SSD1306_I2C(128, 64, i2c_display)
    # display.fill(0)
    # display.text("W25Q128 Writer", 5, 5, 1)
    # display.show()

    for i in range(0, len(tokens), 2):
        addr = int(tokens[i], 0)  # Support hex (0x...), decimal, etc.
        value = int(tokens[i+1], 0)
        write_byte(addr, value)

        if addr > 0 and addr % 1000 == 0:
            print(f"Wrote {value:02X} to {addr:06X}")
            # display.fill(0)
            # display.text("W25Q128 Writer", 5, 5, 1)
            # display.text(f"W {value:02X} to {addr:06X}", 5, 30, 1)
            # display.show()

    # display.fill(0)
    # display.text("W25Q128 Writer", 5, 5, 1)
    # display.text(f"W {value:02X} to {addr:06X}", 5, 30, 1)
    # display.show()


def erase():
    init_spi()
    
    print("Erasing entire W25Q128 chip...")
    chip_erase()
    print("Erase complete")


def erase_sector(sector_addr):
    """Erase a specific 4KB sector"""
    init_spi()
    
    # Align to sector boundary (4KB = 0x1000)
    sector_addr = sector_addr & 0xFFFFF000
    print(f"Erasing sector at {sector_addr:06X}...")
    sector_erase(sector_addr)
    print(f"Sector at {sector_addr:06X} erased")


if __name__ == "__main__":
    write_00_to_ff()

READ:

import machine
import time
import ssd1306
from machine import SPI, Pin

# W25Q128 SPI Flash Configuration
# W25Q128 has 16MB (16777216 bytes) = 128 Mbit

# W25Q128 Commands
CMD_READ_DATA = 0x03
CMD_READ_STATUS = 0x05
CMD_READ_ID = 0x9F
CMD_FAST_READ = 0x0B
CMD_POWER_DOWN = 0xB9
CMD_RELEASE_POWER_DOWN = 0xAB
CMD_RESET_ENABLE = 0x66
CMD_RESET_MEMORY = 0x99

# SPI Configuration
spi = None
cs = None

def init_spi():
    global spi, cs
    # Initialize SPI bus (SPI1) for WeAct BlackPill
    # SCK=PA5, MISO=PA6, MOSI=PA7
    try:
        spi = machine.SPI(
            1,
            baudrate=1000000,
            polarity=0,
            phase=0,
            bits=8,
            firstbit=machine.SPI.MSB,
            sck=machine.Pin('A5'),
            mosi=machine.Pin('A7'),
            miso=machine.Pin('A6'),
        )
    except (ValueError, TypeError):
        try:
            spi = machine.SPI(1, baudrate=1000000, polarity=0, phase=0)
        except Exception:
            spi = machine.SoftSPI(
                baudrate=500000,
                polarity=0,
                phase=0,
                sck=machine.Pin('A5'),
                mosi=machine.Pin('A7'),
                miso=machine.Pin('A6'),
            )

    # CS pin (adjust based on your wiring)
    cs = machine.Pin('A4', machine.Pin.OUT, value=1)
    time.sleep_ms(1)

    flash_wake()
    flash_reset()
    
    # Check device ID
    device_id = read_device_id()
    if device_id == 0x000000:
        time.sleep_ms(5)
        device_id = read_device_id()
    print(f"W25Q128 Device ID: {device_id:06X}")
    if device_id == 0xEF4018:
        print("W25Q128 detected successfully")
    else:
        print(f"Warning: Unexpected device ID: {device_id:06X} (expected 0xEF4018)")

def read_device_id():
    """Read W25Q128 manufacturer and device ID"""
    cs.value(0)
    spi.write(bytes([CMD_READ_ID]))
    id_data = spi.read(3)
    cs.value(1)
    return (id_data[0] << 16) | (id_data[1] << 8) | id_data[2]

def flash_wake():
    """Release from power-down (safe to call even if not asleep)"""
    cs.value(0)
    spi.write(bytes([CMD_RELEASE_POWER_DOWN]))
    cs.value(1)
    time.sleep_ms(1)

def flash_reset():
    """Reset the flash (W25Q series supports 0x66/0x99)"""
    cs.value(0)
    spi.write(bytes([CMD_RESET_ENABLE]))
    cs.value(1)
    time.sleep_us(50)
    cs.value(0)
    spi.write(bytes([CMD_RESET_MEMORY]))
    cs.value(1)
    time.sleep_ms(1)

def read_status():
    """Read status register"""
    cs.value(0)
    spi.write(bytes([CMD_READ_STATUS]))
    status = spi.read(1)[0]
    cs.value(1)
    return status

def read_byte(addr):
    """Read a single byte from address"""
    cs.value(0)
    spi.write(bytes([CMD_READ_DATA, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF]))
    data = spi.read(1)[0]
    cs.value(1)
    return data

def read_bytes(addr, length):
    """Read multiple bytes from address"""
    cs.value(0)
    spi.write(bytes([CMD_READ_DATA, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF]))
    data = spi.read(length)
    cs.value(1)
    return data

def dump_flash(start, length):
    """Dump W25Q128 flash contents"""
    init_spi()
    
    # Initialize display (optional)
    # i2c_display = machine.I2C(1)
    # display = ssd1306.SSD1306_I2C(128, 64, i2c_display)
    # display.fill(0)
    # display.text("W25Q128 Reader", 5, 5, 1)
    # display.show()
    
    for base in range(start, start + length, 16):
        row = [f"{base:06X}:"]

        # if base % 1000 == 0:
        #     display.fill(0)
        #     display.text("W25Q128 Reader", 5, 5, 1)
        #     display.text(f"R {base:06X}", 5, 30, 1)
        #     display.show()

        chunk_len = min(16, (start + length) - base)
        data = read_bytes(base, chunk_len)
        for b in data:
            row.append(f"{b:02X}")
        print(' '.join(row))

if __name__ == "__main__":
    # W25Q128 has 16MB (16777216 bytes)
    # Read first 64KB for testing
    dump_flash(0, 65536)

But here https://buyertrade.taobao.com/trade/itemlist/list_bought_items.htm?spm=a21bo.jianhua/a.bought.1.5af92a89BjwMqR

"""
MPU9250 MicroPython Example
9-axis IMU (Accelerometer, Gyroscope, Magnetometer)
"""

from machine import I2C, Pin
from time import sleep_ms, ticks_ms, ticks_diff
import math

class MPU9250:
    """MPU9250 9-axis IMU driver"""
    
    # MPU9250 I2C address
    MPU9250_ADDRESS = 0x68
    AK8963_ADDRESS = 0x0C
    
    # Register addresses
    PWR_MGMT_1 = 0x6B
    ACCEL_XOUT_H = 0x3B
    GYRO_XOUT_H = 0x43
    TEMP_OUT_H = 0x41
    WHO_AM_I = 0x75
    
    # Magnetometer registers
    MAG_CNTL = 0x0A
    MAG_XOUT_L = 0x03
    MAG_ST1 = 0x02
    MAG_CNTL2 = 0x0B
    MAG_ASAX = 0x10
    
    # Configuration registers
    CONFIG = 0x1A
    GYRO_CONFIG = 0x1B
    ACCEL_CONFIG = 0x1C
    ACCEL_CONFIG2 = 0x1D
    INT_PIN_CFG = 0x37
    USER_CTRL = 0x6A
    
    def __init__(self, i2c, address=MPU9250_ADDRESS):
        self.i2c = i2c
        self.address = address
        
        # Wake up the MPU9250
        self.i2c.writeto_mem(self.address, self.PWR_MGMT_1, b'\x00')
        sleep_ms(100)
        
        # Check WHO_AM_I register
        who_am_i = self.i2c.readfrom_mem(self.address, self.WHO_AM_I, 1)[0]
        if who_am_i != 0x71:
            raise RuntimeError(f"MPU9250 not found. WHO_AM_I: 0x{who_am_i:02X}")
        
        # Configure gyroscope (±250°/s)
        self.i2c.writeto_mem(self.address, self.GYRO_CONFIG, b'\x00')
        
        # Configure accelerometer (±2g)
        self.i2c.writeto_mem(self.address, self.ACCEL_CONFIG, b'\x00')
        
        # Set accelerometer data rate (1kHz) and bandwidth (184Hz)
        self.i2c.writeto_mem(self.address, self.ACCEL_CONFIG2, b'\x01')
        
        # Set gyroscope data rate (1kHz) and bandwidth (184Hz)
        self.i2c.writeto_mem(self.address, self.CONFIG, b'\x01')
        
        # Initialize magnetometer
        self._init_magnetometer()
        
        print("MPU9250 initialized successfully")
    
    def read_accel(self):
        """Read accelerometer data (m/s²)"""
        data = self.i2c.readfrom_mem(self.address, self.ACCEL_XOUT_H, 6)
        
        # Convert to signed 16-bit integers
        ax = self._combine_bytes(data[0], data[1])
        ay = self._combine_bytes(data[2], data[3])
        az = self._combine_bytes(data[4], data[5])
        
        # Scale to g (±2g range, 16384 LSB/g)
        scale = 16384.0
        ax = (ax / scale) * 9.81  # Convert to m/s²
        ay = (ay / scale) * 9.81
        az = (az / scale) * 9.81
        
        return (ax, ay, az)
    
    def read_gyro(self):
        """Read gyroscope data (°/s)"""
        data = self.i2c.readfrom_mem(self.address, self.GYRO_XOUT_H, 6)
        
        # Convert to signed 16-bit integers
        gx = self._combine_bytes(data[0], data[1])
        gy = self._combine_bytes(data[2], data[3])
        gz = self._combine_bytes(data[4], data[5])
        
        # Scale to °/s (±250°/s range, 131 LSB/°/s)
        scale = 131.0
        gx = gx / scale
        gy = gy / scale
        gz = gz / scale
        
        return (gx, gy, gz)
    
    def read_temp(self):
        """Read temperature (°C)"""
        data = self.i2c.readfrom_mem(self.address, self.TEMP_OUT_H, 2)
        temp_raw = self._combine_bytes(data[0], data[1])
        
        # Convert to °C
        temp = (temp_raw / 333.87) + 21.0
        return temp
    
    def _init_magnetometer(self):
        """Initialize AK8963 magnetometer"""
        # Enable I2C master mode and set I2C bypass
        self.i2c.writeto_mem(self.address, self.INT_PIN_CFG, b'\x02')
        sleep_ms(10)
        
        # Power down magnetometer
        self.i2c.writeto_mem(self.AK8963_ADDRESS, self.MAG_CNTL, b'\x00')
        sleep_ms(10)
        
        # Enter fuse ROM access mode
        self.i2c.writeto_mem(self.AK8963_ADDRESS, self.MAG_CNTL, b'\x0F')
        sleep_ms(10)
        
        # Read sensitivity adjustment values
        asa_data = self.i2c.readfrom_mem(self.AK8963_ADDRESS, self.MAG_ASAX, 3)
        self.mag_sensitivity = [(((d - 128) * 0.5) / 128 + 1) for d in asa_data]
        
        # Power down magnetometer
        self.i2c.writeto_mem(self.AK8963_ADDRESS, self.MAG_CNTL, b'\x00')
        sleep_ms(10)
        
        # Set to continuous measurement mode (16-bit, 100Hz)
        self.i2c.writeto_mem(self.AK8963_ADDRESS, self.MAG_CNTL, b'\x16')
        sleep_ms(10)
        
        print("Magnetometer initialized")
    
    def read_mag(self):
        """Read magnetometer data (µT - microtesla)"""
        try:
            # Check if data is ready
            status = self.i2c.readfrom_mem(self.AK8963_ADDRESS, self.MAG_ST1, 1)[0]
            if not (status & 0x01):
                return (0, 0, 0)
            
            # Read magnetometer data (7 bytes: ST1, XL, XH, YL, YH, ZL, ZH)
            data = self.i2c.readfrom_mem(self.AK8963_ADDRESS, self.MAG_XOUT_L, 7)
            
            # Check overflow
            if data[6] & 0x08:
                return (0, 0, 0)
            
            # Convert to signed 16-bit integers (little-endian)
            mx = self._combine_bytes(data[2], data[1])
            my = self._combine_bytes(data[4], data[3])
            mz = self._combine_bytes(data[6], data[5])
            
            # Apply sensitivity adjustment
            mx = mx * self.mag_sensitivity[0] * 0.6  # Convert to µT (4912/32760 * 4)
            my = my * self.mag_sensitivity[1] * 0.6
            mz = mz * self.mag_sensitivity[2] * 0.6
            
            return (mx, my, mz)
        except:
            return (0, 0, 0)
    
    def _combine_bytes(self, msb, lsb):
        """Combine two bytes into signed 16-bit integer"""
        value = (msb << 8) | lsb
        if value >= 0x8000:
            value = -((65535 - value) + 1)
        return value


def main():
    """Example usage of MPU9250"""
    
    # Initialize I2C
    # For ESP32-C6: Adjust pins according to your wiring
    i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400000)
    
    # Scan I2C bus
    print("Scanning I2C bus...")
    devices = i2c.scan()
    print(f"Found devices: {[hex(d) for d in devices]}")
    
    # Initialize MPU9250
    try:
        mpu = MPU9250(i2c)
    except Exception as e:
        print(f"Error initializing MPU9250: {e}")
        return
    
    print("\nReading MPU9250 data...")
    print("Press Ctrl+C to stop\n")
    
    # Initialize angle tracking
    angle_x = 0.0
    angle_y = 0.0
    angle_z = 0.0
    last_time = ticks_ms()
    
    try:
        while True:
            # Calculate time difference
            current_time = ticks_ms()
            dt = ticks_diff(current_time, last_time) / 1000.0  # Convert to seconds
            last_time = current_time
            
            # Read accelerometer
            ax, ay, az = mpu.read_accel()
            
            # Read gyroscope
            gx, gy, gz = mpu.read_gyro()
            
            # Integrate gyroscope to get angles
            angle_x += gx * dt
            angle_y += gy * dt
            angle_z += gz * dt
            
            # Read magnetometer
            mx, my, mz = mpu.read_mag()
            
            # Calculate heading (0-360°)
            heading = math.atan2(my, mx) * 180 / math.pi
            if heading < 0:
                heading += 360
            
            # Read temperature
            temp = mpu.read_temp()
            
            # clear console
            print("\033[2J\033[H", end="")  # ANSI escape codes to clear screen
            
            # Display data
            print("=" * 50)
            print(f"Accelerometer (m/s²):")
            print(f"  X: {ax:7.3f}  Y: {ay:7.3f}  Z: {az:7.3f}")
            print(f"Gyroscope Angles (°):")
            print(f"  Roll:  {angle_x:7.2f}°  (X-axis)")
            print(f"  Pitch: {angle_y:7.2f}°  (Y-axis)")
            print(f"  Yaw:   {angle_z:7.2f}°  (Z-axis)")
            print(f"Gyroscope Speed (°/s):")
            print(f"  X: {gx:7.2f}  Y: {gy:7.2f}  Z: {gz:7.2f}")
            print(f"Magnetometer (µT):")
            print(f"  X: {mx:7.2f}  Y: {my:7.2f}  Z: {mz:7.2f}")
            print(f"Heading: {heading:6.2f}° (0°=North, 90°=East)")
            print(f"Temperature: {temp:.2f}°C")
            
            sleep_ms(100)
            
    except KeyboardInterrupt:
        print("\n\nStopped by user")


if __name__ == "__main__":
    main()

https://item.taobao.com/item.htm?id=42199583243&mi_id=0000ITQ8ab1cso77bC7eJVc4uQGYSsKdCj4cNkfZXQIfyOc&spm=tbpc.boughtlist.suborder_itemtitle.1.50cb2e8dV4wrtN

# tb6612fng_simple.py
# MicroPython example for TB6612FNG + two DC motors (e.g. Tamiya 4WD)
# Works on Pico, ESP32, etc.

from machine import Pin, PWM
from time import sleep

# ────────────────────────────────────────────────
# Pin definitions (change to match YOUR wiring!)
# ────────────────────────────────────────────────

STBY = Pin(22, Pin.OUT)          # Standby pin – must be HIGH to enable driver

# Motor A (usually left motors on Tamiya 4WD)
AIN1 = Pin(18, Pin.OUT)
AIN2 = Pin(19, Pin.OUT)
PWMA = PWM(Pin(20), freq=1000, duty_u16=0)   # PWM freq 1kHz is fine

# ────────────────────────────────────────────────
# Helper functions
# ────────────────────────────────────────────────

def motor_a(speed):
    """
    speed: -1000 to +1000
      positive = forward
      negative = reverse
      0 = stop (coast)
    """
    if speed > 0:
        AIN1.value(1)
        AIN2.value(0)
        PWMA.duty_u16(speed * 65)          # 0→65535 range
    elif speed < 0:
        AIN1.value(0)
        AIN2.value(1)
        PWMA.duty_u16((-speed) * 65)
    else:
        AIN1.value(0)                       # coast stop
        AIN2.value(0)
        PWMA.duty_u16(0)


def drive(left_speed):
    """ left_speed: -1000 to 1000 """
    motor_a(left_speed)


# ────────────────────────────────────────────────
# Main demo
# ────────────────────────────────────────────────

STBY.value(1)  # Enable the driver (important!)
print("TB6612FNG enabled")

try:
    print("Forward slow...")
    drive(700)
    sleep(5)

    print("Forward full...")
    drive(1000)
    sleep(5)

    print("Stop (coast)...")
    drive(0)
    sleep(3)

    print("Backward slow...")
    drive(-700)
    sleep(5)

    print("Backward full...")
    drive(-1000)
    sleep(5)

    print("Brake demo (short brake)...")
    AIN1.value(1);
    AIN2.value(1)    # short brake on A
    PWMA.duty_u16(0)
    sleep(3)

    print("Stop everything")
    drive(0)

finally:
    STBY.value(0)               # Optional: go to low-power standby
    print("Done.")

Setting up the ICESugar FPGA toolchain is trouble in mac, so i built the docker image. This toolchain is for icesugar 40 only, my board is muselab.

Dockerfile is in https://gitlab.quantr.hk/example/chisel/chisel-book/-/blob/master/Dockerfile?ref_type=heads . See this project makefile then you know how to use it https://gitlab.quantr.hk/example/chisel/chisel-book/-/blob/master/example13_icesugar40_rgb_led/Makefile?ref_type=heads

因為想知道Pulseview背後運作原理來,又想為cpu開發加入一啲特別嘅功能,所以決定用STM32自己搞一隻logic analyzer,雖然STM32用來搞不會很高速,但足了之解其原理,之後再用FPGA搞會非常容易

Pulseview是logic analyzer的GUI界面,sigrok-cli是命令行模式,兩者是呼叫一個名為libsigrok的庫去和你的logic analyzer溝通。至於libsigrok用什麼去和你的logic analyzer溝運,這個是你個人意願,因為stm32可以自己變成uart device所以就當它是uart去溝通最容易。你的logic analyzer可以有兩個mode,stream vs buffer。stream就是收幾多signal就立即傳回給libsigrok,buffer就是先儲起直至你認為儲夠再傳回至libsigrok。

我用stm32的F411H750完成,可以看代碼,主要是main.c。因為libsigrok無可能認得你個device,所以要跟這個tutorial去改。最終效果如下

Filters and Power Converters in Electronics – A Brief Overview

In analog electronics, frequency-selective filters are essential building blocks for signal processing. The low-pass filter (LPF) allows low frequencies to pass while attenuating higher ones, making it ideal for noise reduction. Conversely, the high-pass filter (HPF) blocks low frequencies (including DC offset) and passes higher ones. Band-pass filters (BPF) permit only a specific range of frequencies to pass, useful for frequency selection in communication systems, while band-stop/notch filters (BSF) suppress a narrow unwanted frequency band (e.g. 50/60 Hz hum interference). These first- and second-order RC/RL/RLC circuits exhibit characteristic -20 dB/decade or -40 dB/decade roll-off beyond their corner/cutoff frequencies.

On the power electronics side, static converters enable efficient transformation between different voltage types and levels. AC-DC converters (rectifiers) transform alternating current into direct current, forming the front-end of most power supplies. DC-DC converters (such as buck, boost, buck-boost, etc.) step up or step down DC voltage levels and are ubiquitous in battery-powered devices, electric vehicles, and renewable energy systems. DC-AC converters (inverters) convert DC (from batteries, solar panels, or DC links) into AC, powering AC motors, grid-tied solar systems, and uninterruptible power supplies. Finally, AC-AC converters (direct or indirect via DC link) allow voltage magnitude and/or frequency transformation, commonly used in motor drives and power transmission.

Together, these two domains — precise signal filtering and high-efficiency power conversion — form the foundation of modern electronics, spanning audio processing, RF communication, renewable energy systems, electric transportation, and industrial automation.

https://item.taobao.com/item.htm?id=710395566954&mi_id=00006hQxL-b9xEoOFLug5BPJj-kqL5zg0HIfjspeV3U_JWw&spm=tbpc.boughtlist.suborder_itemtitle.1.71512e8dnRLo1o

#include <AccelStepper.h>

// STEP pin, DIR pin
AccelStepper stepper(AccelStepper::DRIVER, 4, 6);

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);

  stepper.moveTo(1);
  stepper.runToPosition();
  delay(500);
  
  digitalWrite(LED_BUILTIN, LOW);
  
  stepper.moveTo(-1);
  stepper.runToPosition();
  delay(500);
}
docker run -it -v .:/chisel-book --name ice40 ubuntu:24.04
apt-get update
export DEBIAN_FRONTEND=noninteractive
apt-get install build-essential clang bison flex libreadline-dev \
                     gawk tcl-dev libffi-dev git mercurial graphviz   \
                     xdot pkg-config python3 libftdi-dev vim \
                     curl openjdk-25-jdk python3-dev libboost-all-dev cmake libeigen3-dev -y
cd /chisel-book
mkdir ice40
cd ice40

git clone https://github.com/YosysHQ/icestorm.git icestorm
cd icestorm
make -j
make install
cd ..

git clone https://github.com/cseed/arachne-pnr.git arachne-pnr
cd arachne-pnr
make -j
make install
cd ..

git clone https://github.com/YosysHQ/nextpnr nextpnr
cd nextpnr
cmake -DARCH=ice40 -DCMAKE_INSTALL_PREFIX=/usr/local . -B build
cd build
make # -j won't work
make install
cd ../..

git clone https://github.com/YosysHQ/yosys.git yosys
cd yosys
git submodule update --init
make -j4
make install
cd ..

Backup

docker exec -it gitlab gitlab-backup create STRATEGY=copy

This creates a single .tar file that contains everything: repositories, database, uploads, builds, artifacts, LFS, registry, pages, etc. The backup file is saved inside the container at $GITLAB_HOME/data/backups

Restore

# 1. Stop GitLab
docker compose down

# 2. Put the .tar file into $GITLAB_HOME/data/backups/

# 3. Restore
docker exec -it gitlab gitlab-backup restore BACKUP=1732301234_2025_11_22_18.6.0

# 4. Start again
docker compose up -d

Tao Bao

https://github.com/quantrpeter/ec11-waveshare-esp32c6

from machine import Pin
import time

class EC11:
    def __init__(self, pin_a, pin_b, pin_c):
        """
        Initialize EC11 rotary encoder for TaoBao version with external pull-ups
        pin_a, pin_b: rotation pins (Terminal A and B)
        pin_c: push button pin (Terminal C)
        
        IMPORTANT: This version has external 10K pull-ups to 5V
        So we use Pin.IN (no internal pull-up)
        """
        # No internal pull-ups since external 10K pull-ups exist
        self.pin_a = Pin(pin_a, Pin.IN)
        self.pin_b = Pin(pin_b, Pin.IN)
        # Use pull-down for button to make logic clearer: pressed=1, unpressed=0
        self.pin_c = Pin(pin_c, Pin.IN, Pin.PULL_DOWN)
        
        # Quadrature state tracking
        self.last_state = (self.pin_a.value() << 1) | self.pin_b.value()
        
        # Lookup table for quadrature decoding
        # Based on state transitions: 00->01->11->10->00 (CW) or reverse (CCW)
        self.state_table = [0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0]
        
        # Counter for rotation
        self.counter = 0
        
        # Accumulator for detent detection (4 steps = 1 detent)
        self.step_accumulator = 0
        
        # Button state tracking
        self.last_button = self.pin_c.value()
        self.button_debounce_time = 0
    
    def read_rotation(self):
        """
        Read rotation direction using quadrature decoding
        Returns: 1 for clockwise, -1 for counter-clockwise, 0 for no change
        """
        # Read current state
        current_state = (self.pin_a.value() << 1) | self.pin_b.value()
        
        # Calculate index for lookup table
        index = (self.last_state << 2) | current_state
        
        # Get direction from lookup table
        direction = self.state_table[index]
        
        # Accumulate steps - only return value after 4 steps (1 detent)
        if direction != 0:
            self.step_accumulator += direction
            self.counter += direction
            
            # Check if we've completed a detent (4 steps in one direction)
            if abs(self.step_accumulator) >= 4:
                result = 1 if self.step_accumulator > 0 else -1
                self.step_accumulator = 0
                self.last_state = current_state
                return result
        
        # Update last state
        self.last_state = current_state
        
        return 0
    
    def read_button(self):
        """
        Read button press with software debouncing
        Returns: True if button was just pressed, False otherwise
        """
        current_time = time.ticks_ms()
        current_button = self.pin_c.value()
        # print('current_button', current_button)
        
        # Button is pressed when pin goes HIGH (from LOW to HIGH)
        # With internal pull-down: unpressed=0, pressed=1
        # Add debouncing: ignore changes within 50ms
        if self.last_button == 0 and current_button == 1:
            if time.ticks_diff(current_time, self.button_debounce_time) > 50:
                self.button_debounce_time = current_time
                self.last_button = current_button
                return True
        
        self.last_button = current_button
        return False
    
    def get_counter(self):
        """Get current counter value"""
        return self.counter
    
    def reset_counter(self):
        """Reset counter to zero"""
        self.counter = 0

# Initialize EC11 with pins 3, 4, 5
encoder = EC11(pin_a=5, pin_b=3, pin_c=4)

print("EC11 Rotary Encoder Test (TaoBao Version)")
print("Pin A: 3, Pin B: 4, Pin C: 5")
print("External 10K pull-ups to 5V with 0.01uF caps")
print("Rotate encoder or press button...")
print("Press Ctrl+C to exit")
print()

try:
    while True:
        # Check rotation
        rotation = encoder.read_rotation()
        if rotation == 1:
            print(f"↻ Clockwise      - Counter: {encoder.get_counter()}")
        elif rotation == -1:
            print(f"↺ Anti-clockwise - Counter: {encoder.get_counter()}")
        
        # Check button press
        if encoder.read_button():
            print(f"🔘 Button pressed! Counter: {encoder.get_counter()} → 0")
            encoder.reset_counter()
        
        time.sleep_ms(1)  # Small delay to prevent excessive polling
        
except KeyboardInterrupt:
    print("\nProgram stopped")
# ws2812_3colors.py
from machine import Pin
from neopixel import NeoPixel
from time import sleep

# WS2812 LED on GPIO 8, 1 pixel
pin = Pin(8, Pin.OUT)
np = NeoPixel(pin, 1)

# Color values in GRB order: (Green, Red, Blue)
RED    = (0, 50, 0)   # Full red
GREEN  = (50, 0, 0)   # Full green
BLUE   = (0, 0, 50)   # Full blue
OFF    = (0, 0, 0)    # LED off

colors = [RED, GREEN, BLUE]

print("Blinking WS2812: Red → Green → Blue")

while True:
    for color in colors:
        np[0] = color
        np.write()
        sleep(0.7)      # Hold each color
        np[0] = OFF
        np.write()
        sleep(0.3)      # Short pause between colors

Category 1: Fundamentals - Geometry, Camera, and Basic Rendering (幾何、相機和基本渲染)

Focus: Core concepts and basic 3D scene setup

Examples to cover (~30):

  • Basics: webgl_geometries, webgl_camera, webgl_camera_array, webgl_camera_logarithmicdepthbuffer
  • Basic Geometry: webgl_geometry_cube, webgl_geometry_shapes, webgl_geometry_colors, webgl_geometry_dynamic
  • Parametric & Advanced: webgl_geometries_parametric, webgl_geometry_convex, webgl_geometry_terrain
  • Text: webgl_geometry_text, webgl_geometry_text_shapes, webgl_geometry_text_stroke
  • Special: webgl_geometry_minecraft, webgl_geometry_teapot, webgl_geometry_nurbs
  • Extrusion: webgl_geometry_extrude_shapes, webgl_geometry_extrude_splines
  • Tools: webgl_helpers, webgl_geometry_spline_editor, webgl_geometry_terrain_raycast
  • CSG: webgl_geometry_csg
  • Lookup: webgl_geometry_colors_lookuptable

Category 2: Materials, Textures & Visual Effects (材質、紋理和視覺效果)

Focus: Materials system, texture mapping, and visual enhancements

Examples to cover (~50):

  • Basic Materials: webgl_materials_blending, webgl_materials_blending_custom, webgl_materials_channels, webgl_materials_wireframe, webgl_materials_toon, webgl_materials_alphahash
  • Texture Mapping: webgl_materials_texture_anisotropy, webgl_materials_texture_canvas, webgl_materials_texture_filters, webgl_materials_texture_manualmipmap, webgl_materials_texture_partialupdate, webgl_materials_texture_rotation
  • Advanced Materials: webgl_materials_bumpmap, webgl_materials_normalmap, webgl_materials_normalmap_object_space, webgl_materials_displacementmap
  • PBR Materials: webgl_materials_physical_clearcoat, webgl_materials_physical_transmission, webgl_materials_physical_transmission_alpha, webgl_materials_subsurface_scattering
  • Environment Maps: webgl_materials_cubemap, webgl_materials_cubemap_dynamic, webgl_materials_cubemap_refraction, webgl_materials_cubemap_mipmaps, webgl_materials_cubemap_render_to_mipmaps, webgl_materials_envmaps, webgl_materials_envmaps_exr, webgl_materials_envmaps_groundprojected, webgl_materials_envmaps_hdr, webgl_materials_matcap
  • Video/Webcam: webgl_materials_video, webgl_materials_video_webcam
  • Special: webgl_materials_car, webgl_materials_modified
  • Effects: webgl_effects_anaglyph, webgl_effects_ascii, webgl_effects_parallaxbarrier, webgl_effects_peppersghost, webgl_effects_stereo
  • Special Rendering: webgl_mirror, webgl_refraction, webgl_portal

Category 3: Lighting & Shadows (光線與陰影)

Focus: Different light types, shadow mapping techniques

Examples to cover (~25):

  • Light Types: webgl_lights_hemisphere, webgl_lights_physical, webgl_lights_pointlights, webgl_lights_spotlight, webgl_lights_spotlights, webgl_lights_rectarealight
  • Light Probes: webgl_lightprobe, webgl_lightprobe_cubecamera
  • Shadows: webgl_shadowmap, webgl_shadowmap_performance, webgl_shadowmap_pointlight, webgl_shadowmap_viewer, webgl_shadowmap_vsm, webgl_shadowmesh, webgl_shadow_contact
  • Advanced Shadows: webgl_shadowmap_csm, webgl_shadowmap_pcss, webgl_shadowmap_progressive
  • Lens Effects: webgl_lensflares
  • Global Illumination: webgl_simple_gi
  • Tone Mapping: webgl_tonemapping
  • Color Space: webgl_test_wide_gamut, webgl_furnace_test, webgl_pmrem_test

Category 4: Animation & Character Control (動畫和角色控制)

Focus: Keyframe animation, skeletal animation, morphing

Examples to cover (~25):

  • Basic Animation: webgl_animation_keyframes, webgl_animation_multiple
  • Skinning: webgl_animation_skinning_blending, webgl_animation_skinning_additive_blending, webgl_animation_skinning_ik, webgl_animation_skinning_morph
  • Morph Targets: webgl_morphtargets, webgl_morphtargets_face, webgl_morphtargets_horse, webgl_morphtargets_sphere, webgl_morphtargets_webcam
  • Animation Groups: misc_animation_groups, misc_animation_keys
  • Instanced Animation: webgl_instancing_morph
  • Modifiers: webgl_modifier_curve, webgl_modifier_curve_instanced, webgl_modifier_edgesplit, webgl_modifier_simplifier, webgl_modifier_subdivision, webgl_modifier_tessellation

Category 5: Interaction & Controls (交互與控制)

Focus: User interaction, raycasting, controls

Examples to cover (~35):

  • Interactive: webgl_interactive_cubes, webgl_interactive_cubes_gpu, webgl_interactive_cubes_ortho, webgl_interactive_buffergeometry, webgl_interactive_lines, webgl_interactive_points, webgl_interactive_raycasting_points, webgl_interactive_voxelpainter
  • Raycasting: webgl_raycaster_bvh, webgl_raycaster_sprite, webgl_raycaster_texture, misc_raycaster_helper
  • Camera Controls: misc_controls_orbit, misc_controls_arcball, misc_controls_fly, misc_controls_map, misc_controls_pointerlock, misc_controls_trackball, misc_controls_transform, misc_controls_drag
  • Selection: misc_boxselection
  • Look At: misc_lookat
  • Layers: webgl_layers
  • Math: webgl_math_obb, webgl_math_orientation_transform

Category 6: Instancing, Performance & Advanced Geometry

Focus: Performance optimization, instancing, buffer geometry

Examples to cover (~50):

  • Instancing: webgl_instancing_dynamic, webgl_instancing_performance, webgl_instancing_raycast, webgl_instancing_scatter
  • Buffer Geometry: webgl_buffergeometry, webgl_buffergeometry_attributes_integer, webgl_buffergeometry_attributes_none, webgl_buffergeometry_custom_attributes_particles, webgl_buffergeometry_drawrange, webgl_buffergeometry_glbufferattribute, webgl_buffergeometry_indexed, webgl_buffergeometry_instancing, webgl_buffergeometry_instancing_billboards, webgl_buffergeometry_instancing_interleaved, webgl_buffergeometry_lines, webgl_buffergeometry_lines_indexed, webgl_buffergeometry_points, webgl_buffergeometry_points_interleaved, webgl_buffergeometry_rawshader, webgl_buffergeometry_selective_draw, webgl_buffergeometry_uint
  • Custom Attributes: webgl_custom_attributes, webgl_custom_attributes_lines, webgl_custom_attributes_points, webgl_custom_attributes_points2, webgl_custom_attributes_points3
  • Points & Particles: webgl_points_billboards, webgl_points_dynamic, webgl_points_sprites, webgl_points_waves
  • Lines: webgl_lines_colors, webgl_lines_dashed, webgl_lines_fat, webgl_lines_fat_raycasting, webgl_lines_fat_wireframe
  • LOD: webgl_lod
  • Batch: webgl_mesh_batch
  • Performance: webgl_performance, webgl_test_memory, webgl_test_memory2
  • Sprites: webgl_sprites

Category 7: File Loaders & Import/Export

Focus: Loading external 3D models and assets

Examples to cover (~70):

  • GLTF (Most Important): webgl_loader_gltf, webgl_loader_gltf_avif, webgl_loader_gltf_compressed, webgl_loader_gltf_dispersion, webgl_loader_gltf_instancing, webgl_loader_gltf_iridescence, webgl_loader_gltf_sheen, webgl_loader_gltf_transmission, webgl_loader_gltf_variants, webgl_loader_gltf_anisotropy
  • Common Formats: webgl_loader_fbx, webgl_loader_fbx_nurbs, webgl_loader_obj, webgl_loader_obj_mtl, webgl_loader_collada, webgl_loader_collada_kinematics, webgl_loader_collada_skinning, webgl_loader_draco, webgl_loader_stl, webgl_loader_ply
  • CAD Formats: webgl_loader_3dm, webgl_loader_3ds, webgl_loader_3mf, webgl_loader_3mf_materials, webgl_loader_amf, webgl_loader_ifc, webgl_loader_usdz, webgl_loader_kmz
  • Animation Formats: webgl_loader_bvh, webgl_loader_md2, webgl_loader_md2_control, webgl_loader_mdd
  • Point Cloud: webgl_loader_pcd, webgl_loader_xyz
  • Scientific: webgl_loader_pdb, webgl_loader_nrrd, webgl_loader_vtk
  • Other: webgl_loader_gcode, webgl_loader_ldraw, webgl_loader_lwo, webgl_loader_svg, webgl_loader_vox, webgl_loader_vrml, webgl_loader_ttf
  • Texture Loaders: webgl_loader_texture_dds, webgl_loader_texture_exr, webgl_loader_texture_ultrahdr, webgl_loader_texture_hdr, webgl_loader_texture_ktx, webgl_loader_texture_ktx2, webgl_loader_texture_lottie, webgl_loader_texture_pvrtc, webgl_loader_texture_rgbm, webgl_loader_texture_tga, webgl_loader_texture_tiff, webgl_loader_imagebitmap
  • Exporters: misc_exporter_draco, misc_exporter_gltf, misc_exporter_obj, misc_exporter_ply, misc_exporter_stl, misc_exporter_usdz, misc_exporter_exr, misc_exporter_ktx2

Category 8: Post-Processing & Advanced Rendering

Focus: Post-processing effects, render targets, advanced techniques

Examples to cover (~60):

  • Basic Post-Processing: webgl_postprocessing, webgl_postprocessing_advanced
  • Anti-Aliasing: webgl_postprocessing_fxaa, webgl_postprocessing_smaa, webgl_postprocessing_ssaa, webgl_postprocessing_taa
  • Bloom & Glow: webgl_postprocessing_unreal_bloom, webgl_postprocessing_unreal_bloom_selective
  • Depth Effects: webgl_postprocessing_dof, webgl_postprocessing_dof2, webgl_postprocessing_ssao, webgl_postprocessing_sao, webgl_postprocessing_gtao, webgl_postprocessing_material_ao
  • Reflections: webgl_postprocessing_ssr
  • Stylistic: webgl_postprocessing_outline, webgl_postprocessing_pixel, webgl_postprocessing_rgb_halftone, webgl_postprocessing_sobel, webgl_postprocessing_glitch, webgl_postprocessing_afterimage
  • Color Grading: webgl_postprocessing_3dlut, webgl_postprocessing_backgrounds, webgl_postprocessing_transition
  • Special Effects: webgl_postprocessing_godrays, webgl_postprocessing_masking, webgl_postprocessing_procedural
  • Render Targets: webgl_rtt, webgl_multiple_rendertargets, webgl_multisampled_renderbuffers
  • Clipping: webgl_clipping, webgl_clipping_advanced, webgl_clipping_intersection, webgl_clipping_stencil, webgl_clipculldistance
  • Depth & Texture: webgl_depth_texture, webgl_framebuffer_texture
  • Advanced Textures: webgl_texture2darray, webgl_texture2darray_compressed, webgl_texture2darray_layerupdate, webgl_texture3d, webgl_texture3d_partialupdate
  • Decals: webgl_decals
  • UBO: webgl_ubo, webgl_ubo_arrays
  • Multiple Views: webgl_multiple_elements, webgl_multiple_elements_text, webgl_multiple_scenes_comparison, webgl_multiple_views
  • Panoramas: webgl_panorama_cube, webgl_panorama_equirectangular, webgl_video_panorama_equirectangular
  • Read Buffer: webgl_read_float_buffer
  • Path Tracing: webgl_renderer_pathtracer
  • Render Target Array: webgl_rendertarget_texture2darray

Category 9: Shaders, GPGPU, Volumes & Special Techniques

Focus: Custom shaders, GPU computing, volumetric rendering

Examples to cover (~35):

  • Custom Shaders: webgl_shader, webgl_shader_lava, webgl_shaders_ocean, webgl_shaders_sky
  • GPGPU: webgl_gpgpu_birds, webgl_gpgpu_birds_gltf, webgl_gpgpu_water, webgl_gpgpu_protoplanet
  • Volumetric: webgl_volume_cloud, webgl_volume_instancing, webgl_volume_perlin
  • Water: webgl_water, webgl_water_flowmap
  • Marching Cubes: webgl_marchingcubes
  • Video: webgl_video_kinect
  • Workers: webgl_worker_offscreencanvas
  • Audio: webaudio_orientation, webaudio_sandbox, webaudio_timing, webaudio_visualizer
  • CSS Integration: css2d_label, css3d_molecules, css3d_orthographic, css3d_periodictable, css3d_sandbox, css3d_sprites, css3d_youtube
  • SVG: svg_lines, svg_sandbox
  • Physics: physics_ammo_break, physics_ammo_cloth, physics_ammo_instancing, physics_ammo_rope, physics_ammo_terrain, physics_ammo_volume, physics_jolt_instancing, physics_rapier_instancing
  • Game: games_fps
  • Tests: misc_uv_tests

Category 10: WebGPU & WebXR (Future of 3D Web)

Focus: Next-generation graphics API and VR/AR

Examples to cover (~180):

WebGPU (~157 examples):

  • Basics: webgpu_camera, webgpu_camera_array, webgpu_camera_logarithmicdepthbuffer, webgpu_sandbox
  • Materials: webgpu_materials, webgpu_materials_basic, webgpu_materials_alphahash, webgpu_materials_arrays, webgpu_materials_displacementmap, webgpu_materials_envmaps, webgpu_materials_envmaps_bpcem, webgpu_materials_lightmap, webgpu_materials_matcap, webgpu_materials_sss, webgpu_materials_transmission, webgpu_materials_toon, webgpu_materials_video, webgpu_clearcoat
  • Lighting: webgpu_lights_custom, webgpu_lights_ies_spotlight, webgpu_lights_phong, webgpu_lights_physical, webgpu_lights_rectarealight, webgpu_lights_selective, webgpu_lights_spotlight, webgpu_lights_tiled, webgpu_lightprobe, webgpu_lightprobe_cubecamera
  • Compute Shaders: webgpu_compute_audio, webgpu_compute_birds, webgpu_compute_geometry, webgpu_compute_particles, webgpu_compute_particles_rain, webgpu_compute_particles_snow, webgpu_compute_points, webgpu_compute_sort_bitonic, webgpu_compute_texture, webgpu_compute_texture_pingpong, webgpu_compute_water
  • TSL (Three Shading Language): webgpu_tsl_angular_slicing, webgpu_tsl_compute_attractors_particles, webgpu_tsl_earth, webgpu_tsl_editor, webgpu_tsl_galaxy, webgpu_tsl_halftone, webgpu_tsl_interoperability, webgpu_tsl_procedural_terrain, webgpu_tsl_raging_sea, webgpu_tsl_transpiler, webgpu_tsl_vfx_flames, webgpu_tsl_vfx_linkedparticles, webgpu_tsl_vfx_tornado
  • Post-Processing: All webgpu_postprocessing_* examples (~30)
  • Advanced: Shadows, instancing, skinning, volumes, particles, loaders, etc.

WebXR (~24 examples):

  • AR: webxr_ar_cones, webxr_ar_hittest, webxr_ar_lighting, webxr_ar_plane_detection
  • VR Interaction: webxr_vr_handinput, webxr_vr_handinput_cubes, webxr_vr_handinput_profiles, webxr_vr_handinput_pointerclick, webxr_vr_handinput_pointerdrag, webxr_vr_handinput_pressbutton
  • VR Experiences: webxr_vr_layers, webxr_vr_panorama, webxr_vr_panorama_depth, webxr_vr_rollercoaster, webxr_vr_sandbox, webxr_vr_teleport, webxr_vr_video
  • XR General: webxr_xr_ballshooter, webxr_xr_controls_transform, webxr_xr_cubes, webxr_xr_dragging, webxr_xr_dragging_custom_depth, webxr_xr_haptics, webxr_xr_paint, webxr_xr_sculpt
  • WebGPU XR: webgpu_xr_cubes

📊 Summary Statistics

CategoryTopic# Examples
1Fundamentals~30
2Materials & Textures~50
3Lighting & Shadows~25
4Animation~25
5Interaction & Controls~35
6Performance & Instancing~50
7Loaders & File I/O~70
8Post-Processing~60
9Shaders & Special~35
10WebGPU & WebXR~180
Total~560

🎯 Teaching Approach Recommendations

  1. Lesson Structure: Each lesson should be 2-3 hours with:
  • Theory introduction (20%)
  • Live coding demonstration (40%)
  • Student hands-on practice (40%)
  1. Key Examples to Demo Live: Focus on 5-8 representative examples per lesson, show others as reference
  1. Progressive Difficulty: Start simple in each lesson, build complexity
  1. Practical Projects: End each lesson with a mini-project combining concepts
  1. WebGPU Note: Lesson 10 is the longest - consider splitting if needed, as WebGPU is the future but may need more dedicated time

Step 1: Install and create the drive

sudo apt install zfsutils-linux
sudo dd if=/dev/zero of=zfsfile.img bs=1 count=0 seek=10G

Step 2: Create pool and view it

sudo zpool create tank /home/peter/zfsfile.img  # have to use full path
sudo zpool status tank
sudo zfs list

Step 3: Optional, use a custom mount point

sudo zfs set mountpoint=/mnt/myzfs tank
sudo zfs mount tank
df -h

Step 4: Create file for test

sudo zfs create tank/testdata  # Creates the dataset 'tank/testdata', auto-mounted at /tank/testdata
cd /tank/testdata  # Or /mnt/myzfs/testdata if custom mount
echo "This is file1 content" | sudo tee testfile1  # No need for full 
echo "This is file1 content" | sudo tee /tank/testdata/testfile1
echo "This is file2 content" | sudo tee /tank/testdata/testfile2
ls /tank/testdata

Step 5: Take snapshot of folder

sudo zfs snapshot tank/testdata@test_snapshot
sudo zfs list -t snapshot  # Verify the snapshot exists

Step 6: Export Snapshot to File

sudo zfs send tank/testdata@test_snapshot > /tmp/test_snapshot.zfs
ls -lh /tmp/test_snapshot.zfs  # Check the backup file

Step 7: Simulate Changes to Test Restore

cd /tank/testdata
sudo rm testfile1  # Delete a file
echo "Modified file2" | sudo tee testfile2  # Change content
echo "New file3 content" | sudo tee testfile3  # Add new file
ls  # Verify changes: testfile2 testfile3

Step 8: Restore folder from snapshot file

sudo zfs destroy tank/testdata@test_snapshot
sudo zfs receive -F tank/testdata &lt; /tmp/test_snapshot.zfs
ls testdata

阿叔之無稽之談:
1. 電容加唔到速:我買左四種電容,全部都有加速效果。阿叔話細電容得2.7v,加唔到速。我用4.8v charge 2.7v電容,加速度好勁。阿叔話會爆炸,我用5v charge,去埋個廁所都等唔到傳說中嘅爆炸!
2. 電容唔夠力:我用5F大約有1.5秒強勁加速,用10F大約有你秒,目測個摩打快左40%以上
3. 四驅車電池不足以為電容充電:我用左個5v升壓,賣2蚊人仔,充電效果唔錯。唯一就係充得唔夠快,5F要20秒,10F要50秒
4. 阿叔話四驅車摩打頂唔到5V:實情係一啲事都無,摩打直頭唔熱,我估去到7v都唔會有問題

Buy : TaoBao

Software download : http://www.yaojiedianzi.com/index.php?m=Download&a=show&id=10

So download here :

Warning: Driver in the zip not work, do this

Install chinese font

VSCode can't find the class from antlr generated sources. We need to run "mvn eclipse:eclipse" to generate .classpath and .project to resolve this problem, otherwise vscode will keep saying your antlr generate classes not found

.classpath

<?xml version="1.0" encoding="UTF-8"?>
<classpath>
  <classpathentry kind="src" path="src/main/java" including="**/*.java"/>
  <classpathentry kind="src" path="target/generated-sources" including="**/*.java"/>
  <classpathentry kind="output" path="target/classes"/>
  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
  <classpathentry kind="var" path="M2_REPO/org/antlr/antlr4-runtime/4.13.1/antlr4-runtime-4.13.1.jar" sourcepath="M2_REPO/org/antlr/antlr4-runtime/4.13.1/antlr4-runtime-4.13.1-sources.jar"/>
  <classpathentry kind="var" path="M2_REPO/org/ow2/asm/asm/9.6/asm-9.6.jar"/>
  <classpathentry kind="var" path="M2_REPO/org/junit/jupiter/junit-jupiter/5.10.2/junit-jupiter-5.10.2.jar"/>
  <classpathentry kind="var" path="M2_REPO/org/junit/jupiter/junit-jupiter-api/5.10.2/junit-jupiter-api-5.10.2.jar"/>
  <classpathentry kind="var" path="M2_REPO/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar"/>
  <classpathentry kind="var" path="M2_REPO/org/junit/platform/junit-platform-commons/1.10.2/junit-platform-commons-1.10.2.jar"/>
  <classpathentry kind="var" path="M2_REPO/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar"/>
  <classpathentry kind="var" path="M2_REPO/org/junit/jupiter/junit-jupiter-params/5.10.2/junit-jupiter-params-5.10.2.jar"/>
  <classpathentry kind="var" path="M2_REPO/org/junit/jupiter/junit-jupiter-engine/5.10.2/junit-jupiter-engine-5.10.2.jar"/>
  <classpathentry kind="var" path="M2_REPO/org/junit/platform/junit-platform-engine/1.10.2/junit-platform-engine-1.10.2.jar"/>
</classpath>

.project

<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
	<name>egg-tart</name>
	<comment>A tiny DSL for stock trading. NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse.</comment>
	<projects>
	</projects>
	<buildSpec>
		<buildCommand>
			<name>org.eclipse.jdt.core.javabuilder</name>
			<arguments>
			</arguments>
		</buildCommand>
	</buildSpec>
	<natures>
		<nature>org.eclipse.jdt.core.javanature</nature>
	</natures>
	<filteredResources>
		<filter>
			<id>1760958095837</id>
			<name></name>
			<type>30</type>
			<matcher>
				<id>org.eclipse.core.resources.regexFilterMatcher</id>
				<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
			</matcher>
		</filter>
	</filteredResources>
</projectDescription>

https://www.waveshare.com/wiki/ESP32-S3-Touch-LCD-3.5

git clone https://github.com/lvgl-micropython/lvgl_micropython.git
cd lvgl_micropython
# git checkout 15a414bc03486017235234882ce7415532c6325e
docker run -it -v .:/micropython --name micropython ubuntu
apt-get update
export DEBIAN_FRONTEND=noninteractive
apt-get install -y gcc g++ make automake python3 git gcc-arm-none-eabi libusb-1.0-0 python3-venv python3-click python3-yaml cmake vim
ln -s /usr/bin/python3 /usr/bin/python
cd /micropython

# old hardware version: waveshare 3.5
python3 make.py esp32 clean \
  --flash-size=16 \
  BOARD=ESP32_GENERIC_S3 \
  BOARD_VARIANT=SPIRAM_OCT \
  DISPLAY=ST7796 \
  INDEV=ft6x36

# new hardware version: waveshare 3.5b
python3 make.py esp32 clean \
  --flash-size=16 \
  BOARD=ESP32_GENERIC_S3 \
  BOARD_VARIANT=SPIRAM_OCT \
  DISPLAY=axs15231b \
  INDEV=axs15231

exit docker 
 
esptool.py --chip esp32s3 -b 460800 \
    --before default_reset \
    --after hard_reset write_flash \
    --flash_mode dio \
    --flash_size 16MB \
    --flash_freq 80m \
    --erase-all 0x0  \
    build/lvgl_micropy_ESP32_GENERIC_S3-SPIRAM_OCT-16.bin

Refer to https://clifford.at/icestorm

icestorm

git clone https://github.com/YosysHQ/icestorm.git icestorm
cd icestorm
make -j$(nproc)
sudo make install
cd ..

arachne-pnr

git clone https://github.com/cseed/arachne-pnr.git arachne-pnr
cd arachne-pnr
make -j$(nproc)
sudo make install
cd ..

nextpnr

git clone https://github.com/YosysHQ/nextpnr nextpnr
cd nextpnr
cmake .  -B build -DARCH=ice40 -DCMAKE_INSTALL_PREFIX=/usr/local
cd build
make -j$(nproc)
sudo make install
cd ../..

yosys

git clone https://github.com/YosysHQ/yosys.git yosys
cd yosys
git submodule update --init
make -j$(nproc)
sudo make install

Very Fast ! ESP32 C6 keep toggling the pin can reach ~1Mhz. Waveshare C6 zero esp32 is running at 160 MHz. https://github.com/quantrpeter/ESP32-C6-Toggle-Pin-Max-Speed

This example controls the WS2812 on Waveshare C6 Zero board, keep changing its color, done using VSCode + ESP-IDF

/* Blink Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "led_strip.h"
#include "sdkconfig.h"

static const char *TAG = "example";

/* Use project configuration menu (idf.py menuconfig) to choose the GPIO to blink,
   or you can edit the following line and set a number here.
*/
#define BLINK_GPIO CONFIG_BLINK_GPIO

static uint8_t s_led_state = 0;
static uint8_t color_phase = 0;  // Phase for color cycling (0-255)

#ifdef CONFIG_BLINK_LED_STRIP

static led_strip_handle_t led_strip;

// Function to create smooth color transitions using HSV to RGB conversion
static void hsv_to_rgb(uint8_t h, uint8_t s, uint8_t v, uint8_t *r, uint8_t *g, uint8_t *b) {
    uint8_t region, remainder, p, q, t;

    if (s == 0) {
        *r = *g = *b = v;
        return;
    }

    region = h / 43;
    remainder = (h - (region * 43)) * 6;

    p = (v * (255 - s)) >> 8;
    q = (v * (255 - ((s * remainder) >> 8))) >> 8;
    t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8;

    switch (region) {
        case 0:
            *r = v; *g = t; *b = p;
            break;
        case 1:
            *r = q; *g = v; *b = p;
            break;
        case 2:
            *r = p; *g = v; *b = t;
            break;
        case 3:
            *r = p; *g = q; *b = v;
            break;
        case 4:
            *r = t; *g = p; *b = v;
            break;
        default:
            *r = v; *g = p; *b = q;
            break;
    }
}

static void blink_led(void)
{
    uint8_t r, g, b;
    
    // Convert HSV to RGB for smooth color transitions
    // Hue cycles through full spectrum (0-255)
    // Saturation = 255 (full saturation for vivid colors)
    // Value = 128 (medium brightness)
    hsv_to_rgb(color_phase, 255, 128, &r, &g, &b);
    
    /* Set the LED pixel with the calculated RGB values */
    led_strip_set_pixel(led_strip, 0, r, g, b);
    /* Refresh the strip to send data */
    led_strip_refresh(led_strip);
    
    // Increment color phase for smooth transition
    color_phase += 2;  // Adjust step size for faster/slower color change
}

static void configure_led(void)
{
    ESP_LOGI(TAG, "Example configured to blink addressable LED!");
    /* LED strip initialization with the GPIO and pixels number*/
    led_strip_config_t strip_config = {
        .strip_gpio_num = BLINK_GPIO,
        .max_leds = 1, // at least one LED on board
    };
#if CONFIG_BLINK_LED_STRIP_BACKEND_RMT
    led_strip_rmt_config_t rmt_config = {
        .resolution_hz = 10 * 1000 * 1000, // 10MHz
        .flags.with_dma = false,
    };
    ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
#elif CONFIG_BLINK_LED_STRIP_BACKEND_SPI
    led_strip_spi_config_t spi_config = {
        .spi_bus = SPI2_HOST,
        .flags.with_dma = true,
    };
    ESP_ERROR_CHECK(led_strip_new_spi_device(&strip_config, &spi_config, &led_strip));
#else
#error "unsupported LED strip backend"
#endif
    /* Set all LED off to clear all pixels */
    led_strip_clear(led_strip);
}

#elif CONFIG_BLINK_LED_GPIO

static void blink_led(void)
{
    /* Set the GPIO level according to the state (LOW or HIGH)*/
    gpio_set_level(BLINK_GPIO, s_led_state);
}

static void configure_led(void)
{
    ESP_LOGI(TAG, "Example configured to blink GPIO LED!");
    gpio_reset_pin(BLINK_GPIO);
    /* Set the GPIO as a push/pull output */
    gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
}

#else
#error "unsupported LED type"
#endif

void app_main(void)
{

    /* Configure the peripheral according to the LED type */
    configure_led();

    while (1) {
        ESP_LOGI(TAG, "Color phase: %d", color_phase);
        blink_led();
        /* Small delay for smooth color transition */
        vTaskDelay(10 / portTICK_PERIOD_MS);  // 10ms delay for smooth animation
    }
}

"screen" command not work, may be the baurate too high, orange pi is using 1.5M. so use this command

picocom -b 1500000 /dev/tty.usbserial-A50285BI

The maximum speed to toggle a pin by python running inside cm4 is just 150khz

Determine the Linux GPIO Number for GPIO3_D1

The Orange Pi CM4 uses a 40-pin header, and GPIO3_D1 corresponds to a specific Linux GPIO number. The RK3566 GPIO numbering follows the formula:

GPIO_number = (bank_number * 32) + (subgroup_letter - 'A') * 8 + pin_number

  • GPIO3_D1:
    • Bank: GPIO3 (bank number 3)
    • Subgroup: D (D - A = 3)
    • Pin: 1
    • Calculation: (3 * 32) + (3 * 8) + 1 = 96 + 24 + 1 = 121

So, GPIO3_D1 is Linux GPIO 121. According to the Orange Pi CM4 pinout, GPIO3_D1 is physical pin 18 on the 40-pin header.

import os
import sys
import time
import signal

# Configuration
GPIO_NUMBER = 121  # GPIO3_D1 (physical pin 18)
LOOP_COUNT = 10000000  # Toggle 10 million times (~5-10s)
BASE_PATH = '/sys/class/gpio'

# Global flag for graceful exit
running = True

def signal_handler(sig, frame):
    global running
    print('\nStopping toggle...')
    running = False

signal.signal(signal.SIGINT, signal_handler)

def main():
    # Check if root
    if os.geteuid() != 0:
        print("Error: Run as root (sudo).", file=sys.stderr)
        sys.exit(1)

    gpio_path = f'{BASE_PATH}/gpio{GPIO_NUMBER}'

    # Export GPIO
    if not os.path.exists(gpio_path):
        try:
            with open(f'{BASE_PATH}/export', 'w') as f:
                f.write(str(GPIO_NUMBER))
            time.sleep(0.1)  # Allow setup
        except IOError as e:
            print(f"Error exporting GPIO {GPIO_NUMBER}: {e}", file=sys.stderr)
            sys.exit(1)

    # Set as output
    try:
        with open(f'{gpio_path}/direction', 'w') as f:
            f.write('out')
    except IOError as e:
        print(f"Error setting direction: {e}", file=sys.stderr)
        sys.exit(1)

    # Open value file for fast I/O in binary mode
    try:
        value_file = open(f'{gpio_path}/value', 'wb', buffering=0)
    except IOError as e:
        print(f"Error opening value file: {e}", file=sys.stderr)
        sys.exit(1)

    print(f"Toggling GPIO {GPIO_NUMBER} (GPIO3_D1, physical pin 18)...")
    print(f"Press Ctrl+C to stop.")
    start_time = time.perf_counter()

    count = 0
    while running and (LOOP_COUNT == 0 or count < LOOP_COUNT):
        value_file.write(b'1')  # Write bytes
        value_file.write(b'0')  # Write bytes
        value_file.flush()      # Ensure write
        count += 1

    end_time = time.perf_counter()
    value_file.close()

    # Cleanup
    try:
        with open(f'{BASE_PATH}/unexport', 'w') as f:
            f.write(str(GPIO_NUMBER))
    except IOError:
        pass

    duration = end_time - start_time
    if duration > 0:
        freq_hz = (count * 2) / duration
        print(f"\nCompleted {count} toggles in {duration:.3f}s ({freq_hz:.0f} Hz).")

if __name__ == '__main__':
    main()

If using C, it reaches 352khz, 2 times faster

This command check the memory base address of GPIO

dtc -I fs /sys/firmware/devicetree/base | grep -i gpio3

Compile:
sudo apt update
sudo apt install build-essential
cat /sys/kernel/debug/gpio
gcc -o toggle_gpio3_d1 toggle_gpio3_d1.c

#include &lt;stdio.h>
#include &lt;stdlib.h>
#include &lt;fcntl.h>
#include &lt;unistd.h>
#include &lt;signal.h>

#define SYSFS_GPIO_PATH "/sys/class/gpio"
#define GPIO_NUM 121  // GPIO3_D1

volatile sig_atomic_t running = 1;

void signal_handler(int sig) {
    running = 0;
}

int main() {
    int fd_export, fd_direction, fd_value;
    char path[64];

    // Check if root
    if (geteuid() != 0) {
        fprintf(stderr, "Error: Run as root (sudo).\n");
        return 1;
    }

    // Export GPIO
    snprintf(path, sizeof(path), "%s/export", SYSFS_GPIO_PATH);
    fd_export = open(path, O_WRONLY);
    if (fd_export &lt; 0) {
        perror("Error exporting GPIO");
        return 1;
    }
    dprintf(fd_export, "%d", GPIO_NUM);
    close(fd_export);
    usleep(100000); // Wait for export

    // Set as output
    snprintf(path, sizeof(path), "%s/gpio%d/direction", SYSFS_GPIO_PATH, GPIO_NUM);
    fd_direction = open(path, O_WRONLY);
    if (fd_direction &lt; 0) {
        perror("Error setting direction");
        return 1;
    }
    write(fd_direction, "out", 3);
    close(fd_direction);

    // Open value file
    snprintf(path, sizeof(path), "%s/gpio%d/value", SYSFS_GPIO_PATH, GPIO_NUM);
    fd_value = open(path, O_WRONLY);
    if (fd_value &lt; 0) {
        perror("Error opening value file");
        return 1;
    }

    printf("Toggling GPIO3_D1 (Linux GPIO %d) at ~500 Hz to find physical pin...\n", GPIO_NUM);
    printf("Test pins 15, 16, 18 with oscilloscope. Press Ctrl+C to stop.\n");

    // Slow toggle for testing
    while (running) {
        write(fd_value, "1", 1);
        //usleep(1000); // 1ms
        write(fd_value, "0", 1);
        //usleep(1000); // 1ms
    }

    // Cleanup
    close(fd_value);
    snprintf(path, sizeof(path), "%s/unexport", SYSFS_GPIO_PATH);
    fd_export = open(path, O_WRONLY);
    if (fd_export >= 0) {
        dprintf(fd_export, "%d", GPIO_NUM);
        close(fd_export);
    }

    printf("GPIO3_D1 unexported\n");
    return 0;
}

Command to toggle pin GPIO3_D1 (GPIO 121)

# Export GPIO 121
echo 121 > /sys/class/gpio/export

# Set as output
echo out > /sys/class/gpio/gpio121/direction

# Toggle manually and check with scope
echo 1 > /sys/class/gpio/gpio121/value
echo 0 > /sys/class/gpio/gpio121/value

# Cleanup
echo 121 > /sys/class/gpio/unexport