Skip to content

Repository files navigation

Arduino core for the i.MX RT1176 (MIMXRT1170-EVKB)

An Arduino/Teensyduino-style core for the NXP i.MX RT1176 crossover MCU, targeting the MIMXRT1170-EVKB evaluation board — dual-core Cortex-M7 @ 996 MHz + Cortex-M4 @ 400 MHz, 2 MB on-chip SRAM, 64 MB SDRAM, 16 MB QSPI flash, on-board WM8962 audio codec, 10/100 Ethernet, dual USB, and the classic Arduino UNO-style header.

Overview

This repository is the board bring-up tree: the example/verification firmwares under examples/ and tooling (tools/). The core itself (teensy-cores, subdir imxrt1176/) and the CMake build glue (teensy-cmake-macros/) are sibling repos, not part of this repository — see "Getting started" below.

  • Teensy 4 heritage. The core is derived from the Teensy 4.x core (PaulStoffregen/cores), ported register-by-register to the RT1176. The familiar API surface works: setup()/loop(), digitalWrite, analogRead, analogWrite (FlexPWM), analogWriteDAC0, tone, attachInterrupt, IntervalTimer, elapsedMillis, Serial/Serial1, SerialUSB, String, EventResponder, DMAChannel, the Teensy Audio graph (AudioStream), USB device (CDC + HID keyboard/mouse/joystick, MIDI) and USB host, and more.
  • Dual-core is first-class. The CM7 boots and manages the CM4 with Multicore (stage/boot/restart/switchImage), talks to it over the MessagingUnit (MU) mailbox/doorbell API, and can keep several CM4 firmware images resident at once and hot-swap between them with Cm4ImageBank (uniform ITCM slots, fast no-copy VTOR switch). CM4 sketches are compiled by the same build system and embedded into the CM7 image. Either core can own the entire audio pipeline (codec, SAI1, the AudioStream graph, CMSIS-DSP) — on the CM4 it runs interrupt-driven, since the audio DMA path is CM7-only, with the CM7 pre-arming the Audio PLL.
  • Everything is verified twice. Each capability has a scripted QEMU gate (a custom QEMU machine model of the RT1176, both cores) and a hardware probe on the real EVKB with un-fakeable assertions. "Silicon wins" — QEMU divergences found on the board are documented, never absorbed silently.
  • Permissive licensing throughout. The firmware tree is MIT/BSD/public- domain; every LGPL file inherited from upstream was replaced with a clean-room rewrite, and tools/license-audit.sh enforces this as a gate (it walks the actual build depfiles, not just the source tree).

Peripheral libraries (Wire, SPI, Audio, SdFat, SD, Ethernet, NativeEthernet, FNET, lwip, USBHost_t36, EEPROM, Bounce2) are resolved local-first with a pinned-GitHub fallback by evkb.cmake: a developer's $TEENSY_LIB_ROOT/<lib> checkout (default ~/Development/<lib>) wins (uncommitted edits included), and when it's absent the library is fetched from GitHub at a SHA pinned in the manifest — so a fresh clone builds with no sibling checkouts at all.

Getting started

Prerequisites

  • macOS (the tree is developed on macOS; paths below reflect that)
  • ARM GCC 10 (arm-none-eabi-gcc) — default path /Applications/ARM_10/bin/; point the ARM_TOOLCHAIN_BIN environment variable at your toolchain's bin directory to override
  • CMake ≥ 3.24
  • NXP LinkServer (e.g. /Applications/LinkServer_26.6.137/) for flashing via the on-board MCU-Link — use LinkServer, not pyOCD (pyOCD is unreliable programming this board's FlexSPI NOR)
  • Optional: the custom qemu-rt1170 (mimxrt1170-evk machine) to run every example without hardware
  • Optional: sibling library checkouts under $TEENSY_LIB_ROOT (default ~/Development/) — used when present, including the core (teensy-cores) and build macros (teensy-cmake-macros); otherwise fetched automatically from GitHub at pinned refs. Set the CPM_SOURCE_CACHE env var (e.g. ~/.cache/CPM) so each repo is cloned once and shared across build directories — the macros themselves are the one exception (~½ MB, plain FetchContent per build dir, deliberate)

Try an example

cd examples/gpio-analog/blink
cmake -B build -DCMAKE_TOOLCHAIN_FILE=../../../toolchain/rt1170-evkb.toolchain.cmake
cmake --build build
./run_qemu.sh        # runs the QEMU gate — asserts the expected UART tokens

The two toolchain files (rt1170-evkb.toolchain.cmake, rt1062-evkb.toolchain.cmake) live once at the repo root, in toolchain/, and are shared by every example — a new example needs no toolchain/ directory of its own, just the ../../../ reach-up to the root.

Build dirs configured before 2026-08-14 cached an absolute toolchain path that no longer exists; their elfs remain valid (gates run them unchanged), but the first reconfigure fails with "toolchain file not found" — rm -rf the build dir and configure fresh with the command above.

Examples are grouped by category under examples/ (dualcore, usb, audio, camera, networking, storage-memory, gpio-analog, timing, serial, display, framework) — see examples/README.md for the full index.

To run every QEMU gate at once (exits non-zero if any fail, so it drops straight into CI):

./tools/run-all-qemu-gates.sh              # all gates, serial
./tools/run-all-qemu-gates.sh dualcore     # only gates matching a pattern
./tools/run-all-qemu-gates.sh -j 4         # parallel (faster; timing-sensitive
                                           # gates can flake under contention)

Gates assume the example is already built; unbuilt ones are reported as SKIP rather than a confusing failure. -l lists what would run, -x stops at the first failure, -h documents the rest.

Flash the board

pkill LinkServer; pkill redlinkserv     # always clear stale probe daemons first
LinkServer flash MIMXRT1176:MIMXRT1170-EVKB load build/<name>.elf

Console output is on the MCU-Link VCOM (/dev/cu.usbmodem…) at 115200. Note: macOS cat resets the port to 9600 — read it with tools/rt1170-console.py <port> 115200 (pyserial; holds the baud and auto-reconnects across board resets) or any terminal program that holds the baud rate. tools/rt1170-flash.sh <image.elf> wraps the flash + console combo, and tools/rt1170-qemu.sh <image.elf> boots an arbitrary image in the QEMU machine outside the gate harness. If the board is silent after a plain flash load, the debug probe left the core halted: power-cycle the board, or use LinkServer run MIMXRT1176:MIMXRT1170-EVKB <name>.elf which loads, resets and free-runs in one step.

Build

The build is plain CMake — no Arduino IDE. The teensy-cmake-macros sibling repo provides the macros; each example is a self-contained consumer project:

cmake_minimum_required(VERSION 3.24)
project(my_sketch)
include(${CMAKE_CURRENT_LIST_DIR}/../../../evkb.cmake)  # macros + cores + manifest

import_evkb_library(Wire)     # optional libraries, by manifest name — local
                              # checkout if present, else pinned GitHub fetch

teensy_add_executable(my_sketch my_sketch.cpp)              # .ino works too
teensy_target_link_libraries(my_sketch cores Wire)
target_link_libraries(my_sketch.elf stdc++)

evkb.cmake bootstraps teensy-cmake-macros, imports the cores library, and defines the pinned manifest (import_evkb_library(<name> [subdirs]), plus evkb_library_dir(<name> OUT_DIR) for cherry-picked sources). Configure with -DEVKB_FORCE_FETCH=ON to ignore all local checkouts and build purely from the pinned GitHub refs (the "fresh user" mode).

Three libraries get dedicated macros instead of the Arduino-style importer, and all three build a plain CMake static-library target, so link them directly rather than through the Teensy macro. import_evkb_cmsis_dsp() and import_evkb_lvgl() are there because those trees are too large for the importer, which globs only one directory level. import_evkb_synthui() is there for a different reason: SynthUI's widgets #include <lvgl.h>, and LVGL's include directories only propagate across a real target_link_libraries edge — which the Teensy macro cannot provide, because it rewrites each name to <name>.o.

import_evkb_lvgl()
teensy_target_link_libraries(my_sketch cores SPI ILI9341_t3)
target_link_libraries(my_sketch.elf LVGL stdc++)

The LVGL sibling repo vendors LVGL 9.4.0 (MIT), pruned of vg_lite_driver (dual-licensed), frogfs (MPL-2.0) and nema_gfx (unlicensed prebuilt .a binaries, which a source-header grep is structurally blind to) so the tree stays MIT/BSD-only — see its VENDORING.md before bumping the pin.

Configure with the board toolchain file (TEENSY_VERSION 117, core clock 996 MHz) plus evkb.cmake, which resolves COREPATH$TEENSY_LIB_ROOT/teensy-cores/imxrt1176/ (linker script imxrt1176.ld, XIP image at 0x30002000):

cmake -B build -DCMAKE_TOOLCHAIN_FILE=../../../toolchain/rt1170-evkb.toolchain.cmake
cmake --build build        # produces my_sketch.elf (+ hex)

CM4 (second core) images are built by the same macros and embedded into the CM7 executable as C arrays:

teensy_add_cm4_image(my_cm4 LINKER cm4.ld SOURCES cm4/startup_cm4.S cm4/main_cm4.c)
teensy_target_link_cm4_image(my_sketch my_cm4)
# or place it in a uniform ITCM slot for use with Cm4ImageBank:
teensy_add_cm4_slot_image(my_cm4 SLOT 0 SLOT_SIZE 0x1000 SOURCES ...)

At runtime the CM7 calls Multicore.begin(my_cm4, sizeof(my_cm4)) to stage and boot it, and exchanges data over MU (see examples/dualcore/).

Status

Everything listed here has both a green QEMU gate and a hardware verification on a real EVKB unless noted.

Area Status
Core runtime (startup, FlexRAM, 996 MHz w/ OverDrive voltage, delay/millis, yield) ✅ HW-verified
Digital GPIO, attachInterrupt, header pin table (LED_BUILTIN = D3) ✅ HW-verified
analogRead (LPADC), analogWrite (FlexPWM), analogWriteDAC0 (DAC12) ✅ HW-verified
tone, IntervalTimer (PIT), elapsedMillis, EventResponder ✅ HW-verified
Serial (LPUART/VCOM), SerialUSB (USB CDC) ✅ HW-verified
Wire (LPI2C master + slave, interrupt-driven) — sibling Wire lib ✅ HW-verified
SPI (LPSPI, blocking + full-duplex DMA/async) — sibling SPI lib ✅ HW-verified
eDMA / DMAChannel (Teensy DMAChannel port) ✅ HW-verified
Audio graph: I2S in/out via SAI1 + WM8962 codec (DMA or interrupt-driven nodes), WAV playback from SD, CMSIS-DSP (FFT/FIR) ✅ HW-verified (audible)
SD card (USDHC/SDIO via SdFat), flash-emulated EEPROM ✅ HW-verified
64 MB SDRAM (SEMC) + extmem_malloc, SNVS RTC ✅ HW-verified
Ethernet 10/100: lwIP stack + Arduino Ethernet API, and FNET/NativeEthernet ✅ HW-verified (DHCP/ping/TCP/UDP/DNS)
USB device: CDC + HID keyboard/mouse/joystick composite, MIDI ✅ HW-verified
USB host: HID (keyboard/mouse via hub), MIDI, mass storage r/w ✅ HW-verified
FlexCAN (CAN3 on J47), ST7735 display ✅ HW-verified
PXP 2D blitter (fill/blit/rotate/flip, sync+async) — sibling PXP lib ✅ HW-verified
LVGL 9.4 GUI (MIT) — sibling LVGL lib, software render, ILI9341 (SPI) binding ✅ HW-verified (widgets on the glass; silicon render checksum bit-identical to the QEMU golden)
LVGL 9.4 on the RPi 7" MIPI-DSI panel (800×480, direct render into the LCDIFv2 scanout buffer) ⚠️ QEMU-gated only — panel not yet connected, unconfirmed on glass
LVGL 9.4 on the RK055HDMIPI4MA0 5.5" MIPI-DSI panel (720×1280, direct render) ✅ HW-verified (its render golden is human-confirmed on glass — the RPi LVGL golden above pins reproducibility only)
LVGL pointer indev over the GT911 touch controller (RK055): taps, drag, two-finger press/release ✅ HW-verified (asserts LVGL's reaction — widget state/position — not pixels; double-buffered as of v5)
LVGL double buffering + page flip on vsync on the RK055 (lvgl_mipi_panel_create_db) ✅ HW-verified (tearing demonstrated single-buffered, gone double-buffered; QEMU proves the panel scanned each buffer in turn via the vsync-latched shadow load; as of v5 the flip fence is ISR-fenced — the first LCDIFv2 interrupt taken on this silicon)
PXP-accelerated LVGL cross-buffer sync copy (lvgl_pxp_copy) ✅ HW-verified — adopted on a measured 5–21× win (full-screen sync: 89.5 ms CPU vs 12.3 ms PXP; single-row copies stay on the CPU by the same measured rule). The bench also found and forced a QEMU PXP model fix: the model silently truncated copies at 1024 rows
Dual-core: CM4 boot (Multicore), MU IPC, CM4 GPIO/SPI/I2C, CM4 interrupt + DMA I/O (eDMA_LPSR), runtime hot-swap, Cm4ImageBank multi-image slots ✅ HW-verified
CM4-owned audio: the CM4 alone drives the WM8962 codec, SAI1 (interrupt-driven nodes), the AudioStream graph, and CMSIS-DSP FFT — with the CM7 idle (zero audio IRQs) ✅ HW-verified (audible 1 kHz on J101; CM7 pre-arms the Audio PLL)
CrashReport, MTP, USB audio/touch/rawhid/flightsim headers ⚠️ present in tree, not verified on this board

Limitations

  • One board. Only the MIMXRT1170-EVKB (RT1176) is supported — pin tables, linker script, clocks and the flash layout are board-specific. No Arduino IDE / arduino-cli integration; the build is CMake-only.
  • macOS-centric tooling. LinkServer paths and the default toolchain location are macOS-flavoured (the compiler is overridable via ARM_TOOLCHAIN_BIN); other hosts may need small toolchain-file edits. The serial console must be read with something that holds 115200 (macOS cat drops it to 9600).
  • Pinned-manifest maintenance. Fresh-clone builds fetch libraries at SHAs pinned in evkb.cmake; after pushing new library work, the pin must be updated by hand (push → paste the new SHA → commit) or fresh users keep building the older ref.
  • CM4 constraints. The main eDMA's completion interrupts are wired to the CM7 only — CM4 interrupt-driven DMA requires the eDMA_LPSR instance and an LPSR-domain peripheral (LPI2C5/6, LPSPI5/6, LPUART11/12…). The CM4's fast GPIO ports (GPIO7-12 aliases) have no CM4 interrupt; DMA cannot reach the CM4's private TCM (use OCRAM for buffers). CM4 images are position-dependent (linked for a fixed ITCM address). No cross-core peripheral arbitration protocol yet — assign each peripheral instance to one core.
  • DMA audio is CM7-only. Main-eDMA completion IRQs are CM7-domain, so the DMA-driven input_i2s/output_i2s nodes only run on the CM7. The CM4 owns audio a different way — the interrupt-driven AudioInputI2SInt/ AudioOutputI2SInt nodes (SAI FIFO IRQ, no DMA) — with the CM7 pre-arming the Audio PLL (the CM4 can't drive the ANATOP AI-write handshake). One core owns the pipeline per firmware; there's no simultaneous dual-core audio.
  • Board traps worth knowing. Header pin A5 (GPIO_AD_08, also LPI2C1_SCL) is wired to USB_OTG2_ID — plugging a USB-OTG adapter into the second USB port clamps A5/SCL to 0 V and silently kills header I²C. FlexCAN RX mailboxes lock when their C/S word is read; drain via the proper sequence rather than tight-polling.
  • QEMU is a gate, not an oracle. The qemu-rt1170 model doesn't enforce clock gating/pin muxing everywhere and stubs some devices (e.g. codec register reads); several real bugs only ever reproduced on silicon. Hardware remains the final arbiter — treat a QEMU pass as necessary, not sufficient.
  • USB device classes beyond CDC/HID/MIDI (audio, touch, rawhid, flightsim, MTP) are inherited headers, not yet ported/verified on this board.

About

Arduino core + 57 examples for the NXP i.MX RT1176 (MIMXRT1170-EVKB): dual-core CM7+CM4, QEMU-gated, hardware-verified

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages