diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4db8e60 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.o +*.elf +*.img +bootcode.bin +start.elf +kernel.map \ No newline at end of file diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..378c8ae --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,34 @@ +# Stupid Synth Design Document + +## Overview +Stupid Synth is a minimalist, 4-voice polyphonic synthesizer designed for the RP2040. It prioritizes zero external component count (other than the sound transducer itself) and code simplicity. + +## Hardware Design +* **MCU**: Raspberry Pi Pico (RP2040). +* **Audio Output**: + * **Method**: High-speed PWM (Pulse Width Modulation). + * **Pin**: GP28. + * **Circuit**: Connect a piezo buzzer directly between GP28 and GND, or a speaker/headphones in series with a 220Ω resistor. +* **Input**: USB Serial Console (no physical buttons/pots required). + +## Software Architecture +### Audio Engine +* **Carrier Frequency**: ~490 kHz (125MHz sys clock / 255 wrap). This pushes PWM noise far above human hearing, minimizing the need for an analog filter. +* **Sample Rate**: 22,050 Hz. +* **Bit Depth**: 8-bit. + +### Synthesis Engine +* **Waveform**: Sawtooth (generated via simple phase accumulation). +* **Polyphony**: 4 simultaneous voices. +* **Envelope**: Simple Decay (percussive/plucked sound). +* **Mixing**: Tanh saturation (soft clipping) to prevent digital distortion when voices sum. + +### Interface +* **Control**: Keyboard characters sent via Serial Monitor trigger notes. + * Keys: `a`, `s`, `d`, `f`, `g`, `h`, `j`, `k` (Scale C4 - C5). + +## Execution Plan +1. Configure RP2040 Hardware PWM on GP28. +2. Setup a repeating timer interrupt at 22.05kHz. +3. Implement the mixing loop and voice management in the interrupt handler. +4. Process Serial input in the main loop. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a14193f --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +TOOLCHAIN = arm-none-eabi +CC = $(TOOLCHAIN)-gcc +OBJCOPY = $(TOOLCHAIN)-objcopy + +all: kernel.img + +kernel.img: boot.o kernel.o + $(CC) -T link.ld -o kernel.elf -ffreestanding -O2 -nostdlib boot.o kernel.o + $(OBJCOPY) kernel.elf -O binary kernel.img + +clean: + rm -f *.o *.elf *.img \ No newline at end of file diff --git a/README.md b/README.md index 753ace4..0e42fd3 100644 --- a/README.md +++ b/README.md @@ -22,70 +22,50 @@ It boots in under two seconds and produces sound and graphics with minimal exter ### Software -- **ARM Cross-Compiler**: The `arm-none-eabi-gcc` toolchain is required to build the kernel. -- **Raspberry Pi Firmware**: You need the `bootcode.bin` and `start.elf` files from the official Raspberry Pi firmware repository. +- **Distrobox**: Required for the automated setup script. It works on most Linux distributions and is pre-installed on systems like Fedora Silverblue/Kinoite and Bazzite. +- The provided scripts handle all other dependencies. -## Quick Start with Scripts +## Quick Start (Recommended) -For a fast and easy setup, you can use the provided automation scripts. +This project includes scripts to automate the entire setup and deployment process. -1. **Build the kernel**: - ```sh - ./build.sh - ``` - This will compile the C and Assembly code and create the `kernel.img` file. +### 1. Set Up the Development Environment (One-Time Setup) -2. **Deploy to SD Card**: - Insert your FAT32-formatted SD card. Find its device name (e.g., `/dev/sdX` or `/dev/mmcblkX`) and mount point. - - ```sh - # Example: ./deploy.sh /media/user/RASPIBOOT - ./deploy.sh - ``` - This script copies `kernel.img`, `bootcode.bin`, and `start.elf` to the SD card and then safely unmounts it. - -## Manual Setup and Deployment - -### 1. Install Toolchain - -On Debian/Ubuntu-based systems, you can install the cross-compiler with: +The `setup-dev-env.sh` script creates a self-contained environment with all the necessary build tools, without altering your host operating system. ```sh -sudo apt-get update -sudo apt-get install gcc-arm-none-eabi binutils-arm-none-eabi +./setup-dev-env.sh ``` -### 2. Get Raspberry Pi Firmware +### 2. Build and Deploy to SD Card -The Pi's GPU first loads firmware from the SD card before handing control to the ARM core. You need two files: +Insert a FAT32-formatted SD card so that it is mounted by your system. The `deploy.sh` script will then build the kernel, download the necessary firmware, and interactively ask you which device to deploy to. -- `bootcode.bin`: The second-stage bootloader. -- `start.elf`: The GPU firmware. - -You can download them from the official Raspberry Pi firmware repository. Make sure to grab the files from the `boot` directory. For this project, you only need these two files. - -Place them in the root of this project directory so the `deploy.sh` script can find them. - -### 3. Build the Kernel - -With the toolchain installed, compile the synthesizer by running `make`: +Run the script with `sudo` because it needs permission to unmount the drive when it's finished. ```sh -make +sudo ./deploy.sh ``` -This command uses the `Makefile` to compile `boot.S` and `kernel.c`, linking them into a final binary image named `kernel.img`. +Follow the on-screen prompts to select your SD card. The script handles the rest. -### 4. Prepare the SD Card +## How the Automation Works -Your SD card must be formatted with a **FAT32** filesystem. Most new SD cards are already formatted this way. The partition should also have the "boot" or "lba" flag set, which is standard for bootable Raspberry Pi cards. +- **`setup-dev-env.sh`**: This script uses Distrobox to create a lightweight Ubuntu container named `pi-synth-dev`. Inside this container, it installs the `make` and `gcc-arm-none-eabi` toolchain required for cross-compiling. This isolates dependencies and keeps your host system clean. -### 5. Deploy to SD Card +- **`deploy.sh`**: This script orchestrates the entire build and deployment process from your host machine: + 1. **Builds the Kernel**: It automatically enters the `pi-synth-dev` container to run `build.sh`, which compiles the source code into `kernel.img`. + 2. **Fetches Firmware**: It checks for `bootcode.bin` and `start.elf`. If they are missing, it downloads them from the official Raspberry Pi firmware repository. + 3. **Finds the SD Card**: It scans for *mounted* removable drives and presents an interactive menu for you to choose the correct SD card partition. + 4. **Copies Files**: It copies `kernel.img`, `bootcode.bin`, and `start.elf` to the selected SD card. + 5. **Unmounts**: It safely unmounts the SD card, making it ready for use in the Raspberry Pi. -Mount the SD card and copy the three essential files to its root directory: +## Manual Alternative -- `bootcode.bin` (from the firmware repo) -- `start.elf` (from the firmware repo) -- `kernel.img` (generated by the `make` command) +If you prefer not to use the scripts, you can perform the steps manually. + +1. **Install Toolchain**: Install `gcc-arm-none-eabi` and `make` on your system. +2. **Get Firmware**: Download `bootcode.bin` and `start.elf` from the official Raspberry Pi firmware repository. +3. **Build**: Run `make` to compile `kernel.img`. +4. **Deploy**: Copy `kernel.img`, `bootcode.bin`, and `start.elf` to the root of a FAT32-formatted SD card. -After copying, unmount the SD card safely. ## Hardware Connections diff --git a/boot.S b/boot.S new file mode 100644 index 0000000..3e77149 --- /dev/null +++ b/boot.S @@ -0,0 +1,21 @@ +.cpu arm1176jzf-s +.section ".text.boot" + +.global _start + +_start: + // 1. Set up the stack pointer + // The Pi 1 has 512MB RAM (usually), stack grows down from a safe high address. + // We'll set it to 0x8000 (where the kernel starts) and let it grow down. + mov sp, #0x8000 + + // 2. Clear BSS (Uninitialized variables) - Skipped for minimal "Stupid" version + // but good practice usually. + + // 3. Jump to C code + bl kernel_main + + // 4. Halt if main returns +halt: + wfe + b halt \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..69ae3f9 --- /dev/null +++ b/build.sh @@ -0,0 +1,6 @@ +#!/bin/bash +echo "Building kernel.img..." +make +if [ $? -eq 0 ]; then + echo "Build successful." +fi \ No newline at end of file diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..f31852a --- /dev/null +++ b/deploy.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +MOUNT_POINT=$1 + +# Function to fetch firmware dependencies +fetch_dependencies() { + FIRMWARE_URL="https://github.com/raspberrypi/firmware/raw/master/boot" + REQUIRED_FILES="bootcode.bin start.elf" + + for f in $REQUIRED_FILES; do + if [ ! -f "$f" ]; then + echo "File '$f' not found. Downloading from official repo..." + if command -v wget >/dev/null 2>&1; then + wget -q --show-progress "$FIRMWARE_URL/$f" -O "$f" + elif command -v curl >/dev/null 2>&1; then + curl -L -o "$f" "$FIRMWARE_URL/$f" + else + echo "Error: Neither wget nor curl found. Cannot download $f." + exit 1 + fi + fi + done +} + +if [ -z "$MOUNT_POINT" ]; then + if command -v lsblk >/dev/null 2>&1; then + echo "Scanning for mounted removable storage..." + OPTIONS=() + MOUNTPOINTS=() + while IFS= read -r line; do + eval "$line" + if [ "$RM" == "1" ] && [ -n "$MOUNTPOINT" ]; then + OPTIONS+=("$NAME ($SIZE) mounted at $MOUNTPOINT") + MOUNTPOINTS+=("$MOUNTPOINT") + fi + done < <(lsblk -P -o NAME,SIZE,MOUNTPOINT,RM) + + if [ ${#OPTIONS[@]} -eq 0 ]; then + echo "No mounted removable devices found." + exit 1 + fi + + echo "Select the SD card partition to deploy to:" + select opt in "${OPTIONS[@]}"; do + if [ -n "$opt" ]; then + MOUNT_POINT="${MOUNTPOINTS[$((REPLY-1))]}" + break + else + echo "Invalid selection." + fi + done + else + echo "Usage: $0 " + echo "Example: $0 /media/user/RASPIBOOT" + exit 1 + fi +fi + +if [ ! -d "$MOUNT_POINT" ]; then + echo "Error: Mount point '$MOUNT_POINT' not found or is not a directory." + exit 1 +fi + +fetch_dependencies || exit 1 + +distrobox enter "pi-synth-dev" -- ./build.sh || exit 1 + +echo "Deploying to $MOUNT_POINT..." + +for f in kernel.img bootcode.bin start.elf; do + if [ ! -f "$f" ]; then + echo "Error: Required file '$f' not found. Make sure you have run ./build.sh and downloaded the firmware." + exit 1 + fi + echo "Copying $f..." + cp "$f" "$MOUNT_POINT/" +done + +echo "Deployment complete. Unmounting..." +sync # Ensure all writes are finished +umount "$MOUNT_POINT" +echo "SD card is safe to remove." \ No newline at end of file diff --git a/kernel.c b/kernel.c new file mode 100644 index 0000000..4c072e7 --- /dev/null +++ b/kernel.c @@ -0,0 +1,294 @@ +#include + +// --- BCM2835 PERIPHERAL ADDRESSES --- +// Base address for Pi 1 is 0x20000000 +#define PERIPHERAL_BASE 0x20000000 +#define GPIO_BASE (PERIPHERAL_BASE + 0x200000) +#define PWM_BASE (PERIPHERAL_BASE + 0x20C000) +#define CLK_BASE (PERIPHERAL_BASE + 0x101000) +#define UART0_BASE (PERIPHERAL_BASE + 0x201000) // PL011 UART + +// --- MAILBOX REGISTERS (VIDEO) --- +#define MBOX_READ (PERIPHERAL_BASE + 0xB880) +#define MBOX_STATUS (PERIPHERAL_BASE + 0xB898) +#define MBOX_WRITE (PERIPHERAL_BASE + 0xB8A0) + +// --- REGISTER ACCESS MACROS --- +#define MMIO32(addr) (*(volatile uint32_t *)(addr)) + +// --- GPIO REGISTERS --- +#define GPFSEL0 (GPIO_BASE + 0x00) +#define GPFSEL1 (GPIO_BASE + 0x04) +#define GPFSEL4 (GPIO_BASE + 0x10) // For Pin 40, 45 + +// --- PWM REGISTERS --- +#define PWM_CTL (PWM_BASE + 0x00) +#define PWM_STA (PWM_BASE + 0x04) +#define PWM_RNG1 (PWM_BASE + 0x10) +#define PWM_DAT1 (PWM_BASE + 0x14) +#define PWM_RNG2 (PWM_BASE + 0x20) +#define PWM_DAT2 (PWM_BASE + 0x24) +#define PWM_FIFO (PWM_BASE + 0x18) + +#define PWM_CTL_PWEN1 (1 << 0) +#define PWM_CTL_USEF1 (1 << 5) // Use FIFO for channel 1 +#define PWM_CTL_PWEN2 (1 << 8) +#define PWM_CTL_USEF2 (1 << 13) // Use FIFO for channel 2 +#define PWM_CTL_CLRF (1 << 6) +#define PWM_STA_FULL1 (1 << 0) + +// --- CLOCK MANAGER --- +#define CM_PWMCTL (CLK_BASE + 0xA0) +#define CM_PWMDIV (CLK_BASE + 0xA4) +#define CM_PASSWD (0x5A << 24) + +// --- UART REGISTERS --- +#define UART0_DR (UART0_BASE + 0x00) +#define UART0_FR (UART0_BASE + 0x18) +#define UART0_IBRD (UART0_BASE + 0x24) +#define UART0_FBRD (UART0_BASE + 0x28) +#define UART0_LCRH (UART0_BASE + 0x2C) +#define UART0_CR (UART0_BASE + 0x30) + +// --- UTILS --- +void delay(int32_t count) { + while (count--) asm volatile("nop"); +} + +void gpio_set_func(uint32_t pin, uint32_t func) { + uint32_t offset = (pin / 10) * 4; + uint32_t shift = (pin % 10) * 3; + uint32_t addr = GPIO_BASE + offset; + + uint32_t val = MMIO32(addr); + val &= ~(7 << shift); + val |= (func << shift); + MMIO32(addr) = val; +} + +void uart_init() { + // Disable UART + MMIO32(UART0_CR) = 0; + + // Setup GPIO 14/15 as Alt0 (UART0 TX/RX) + gpio_set_func(14, 4); // Alt0 + gpio_set_func(15, 4); // Alt0 + + // Baud rate setup (assuming 3MHz default UART clock) + // For 115200: 3000000 / (16 * 115200) = 1.627 + // IBRD = 1 + // FBRD = 0.627 * 64 = 40 + MMIO32(UART0_IBRD) = 1; + MMIO32(UART0_FBRD) = 40; + + // 8 bit, no parity, FIFO enabled + MMIO32(UART0_LCRH) = (1 << 4) | (1 << 5) | (1 << 6); + + // Enable UART, TX, RX + MMIO32(UART0_CR) = (1 << 0) | (1 << 8) | (1 << 9); +} + +char uart_read_byte() { + // Check RX FIFO empty flag + if (MMIO32(UART0_FR) & (1 << 4)) return 0; + return (char)(MMIO32(UART0_DR) & 0xFF); +} + +void pwm_audio_init() { + // 1. Setup GPIO 40 and 45 to Alt0 (PWM) + // Pi 1 Model B uses 40/45. Model B+ uses 40/41. + // We enable all of them to cover both revisions. + gpio_set_func(40, 4); // PWM0 + gpio_set_func(41, 4); // PWM1 + gpio_set_func(45, 4); // PWM1 + + // 2. Setup PWM Clock + // Kill the clock completely first + MMIO32(CM_PWMCTL) = CM_PASSWD | 0; + // Wait for busy flag to clear + while (MMIO32(CM_PWMCTL) & 0x80) delay(1); + + // Set Divider. 19.2MHz / 2 = 9.6MHz. + // Using divisor 2 is safer for the clock generator. + MMIO32(CM_PWMDIV) = CM_PASSWD | (2 << 12); + + // Start Clock (Source 1 = Oscillator 19.2MHz) + MMIO32(CM_PWMCTL) = CM_PASSWD | 0x11; // Enable + Source 1 + + // 3. Configure PWM + // Clock is 9.6MHz. Target ~44.1kHz. + // 9600000 / 44100 = ~218 + MMIO32(PWM_RNG1) = 218; + MMIO32(PWM_RNG2) = 218; + + // Enable PWM0 and PWM1, Use FIFO for both + MMIO32(PWM_CTL) = PWM_CTL_CLRF; // Clear FIFO + delay(100); + MMIO32(PWM_CTL) = PWM_CTL_PWEN1 | PWM_CTL_USEF1 | PWM_CTL_PWEN2 | PWM_CTL_USEF2; +} + +// --- VIDEO / FRAMEBUFFER DRIVER --- +// The GPU handles composite/HDMI. We just ask for a memory buffer to draw in. + +volatile uint32_t __attribute__((aligned(16))) mailbox[36]; + +uint32_t fb_width, fb_height, fb_pitch; +uint16_t *fb_ptr = 0; // Pointer to the screen pixels (RGB565) + +void mbox_write(uint8_t channel, uint32_t value) { + while (MMIO32(MBOX_STATUS) & 0x80000000); // Wait while full + MMIO32(MBOX_WRITE) = (value & ~0xF) | (channel & 0xF); +} + +uint32_t mbox_read(uint8_t channel) { + while (1) { + while (MMIO32(MBOX_STATUS) & 0x40000000); // Wait while empty + uint32_t data = MMIO32(MBOX_READ); + if ((data & 0xF) == channel) return data & ~0xF; + } +} + +void video_init() { + // Property Tag Interface to GPU + mailbox[0] = 35 * 4; // Total size + mailbox[1] = 0; // Request code + + mailbox[2] = 0x00048003; // Set Physical Size + mailbox[3] = 8; mailbox[4] = 8; mailbox[5] = 640; mailbox[6] = 480; + + mailbox[7] = 0x00048004; // Set Virtual Size + mailbox[8] = 8; mailbox[9] = 8; mailbox[10] = 640; mailbox[11] = 480; + + mailbox[12] = 0x00048005; // Set Depth + mailbox[13] = 4; mailbox[14] = 4; mailbox[15] = 16; // 16-bit (RGB565) + + mailbox[16] = 0x00040001; // Allocate Buffer + mailbox[17] = 8; mailbox[18] = 8; mailbox[19] = 4096; mailbox[20] = 0; + + mailbox[21] = 0x00040008; // Get Pitch + mailbox[22] = 4; mailbox[23] = 4; mailbox[24] = 0; + + mailbox[25] = 0; // End Tag + + // Send to Mailbox Channel 8 (Tags) + // Add 0x40000000 to address to tell GPU to bypass L2 cache (ensure coherency) + mbox_write(8, (uint32_t)&mailbox | 0x40000000); + mbox_read(8); + + if (mailbox[1] == 0x80000000) { // Success + // Convert GPU bus address (0x4XXXXXXX) to ARM physical address (0x0XXXXXXX) + fb_ptr = (uint16_t*)(mailbox[19] & 0x3FFFFFFF); + fb_width = mailbox[5]; + fb_height = mailbox[6]; + fb_pitch = mailbox[24]; + } +} + +void draw_rect(int x, int y, int w, int h, uint16_t color) { + if (!fb_ptr) return; + for (int j = y; j < y + h; j++) { + if (j < 0 || j >= fb_height) continue; + // Calculate row start: fb_ptr + (j * pitch / 2) because pitch is bytes + uint16_t *row = (uint16_t*)((uint8_t*)fb_ptr + j * fb_pitch); + for (int i = x; i < x + w; i++) { + if (i >= 0 && i < fb_width) { + row[i] = color; + } + } + } +} + +// --- MAIN KERNEL --- +void kernel_main(void) { + uart_init(); + pwm_audio_init(); + video_init(); + + // Draw a background + draw_rect(0, 0, 640, 480, 0x0000); // Black + draw_rect(10, 10, 620, 460, 0x001F); // Blue border box + + // USB Note + // (USB requires a massive host stack, sticking to UART for input) + + // Synth State + uint32_t phase = 0; + uint32_t increment = 0; + int amplitude = 0; // Use signed for decay + + // Frequencies (scaled for 44.1kHz sample rate and 32-bit phase accumulator) + // Inc = (Freq * 2^32) / SampleRate + // C4 (261.63) -> ~25466000 + const uint32_t note_C4 = 25466000; + const uint32_t note_E4 = 32090000; + const uint32_t note_G4 = 38160000; + const uint32_t note_A4 = 42837000; + const uint32_t note_C5 = 50932000; + const uint32_t notes[] = { note_C4, note_E4, note_G4, note_A4, note_C5 }; + + int bar_height = 0; + uint32_t sample_count = 0; + uint32_t next_event = 0; + uint32_t rng = 0x12345678; + + while (1) { + // 1. Sequencer and Envelope + if (sample_count >= next_event) { + rng = rng * 1664525 + 1013904223; // Simple LCG + int note_idx = (rng >> 24) % 5; + increment = notes[note_idx]; + amplitude = 200; // Start amplitude (max height for visualizer) + + // Duration: ~0.1s to 0.5s (4410 to 22050 samples) + next_event = sample_count + 4410 + ((rng >> 10) % 17640); + } else { + // Simple amplitude decay + // Decay every 200 samples (~4.5ms) to prevent clicking + if (sample_count % 200 == 0) { + if (amplitude > 0) { + amplitude--; + } else { + increment = 0; // Stop note when amplitude is zero + } + } + } + sample_count++; + + // 2. Update Graphics (Simple visualizer) + // Only update every 1000 samples (~22ms / 44fps) to keep audio smooth + if (sample_count % 1000 == 0 && amplitude != bar_height) { + if (amplitude > bar_height) { + // Growing: Draw red on top + draw_rect(300, 240 - amplitude, 40, amplitude - bar_height, 0xF800); + } else { + // Shrinking: Draw black on top + draw_rect(300, 240 - bar_height, 40, bar_height - amplitude, 0x0000); + } + bar_height = amplitude; + } + + // 3. Wait for FIFO space + // The hardware consumes data at 44.1kHz. + // By waiting for space, we automatically sync to that speed. + // Added timeout to prevent hang if clock fails + int timeout = 2000; + while ((MMIO32(PWM_STA) & PWM_STA_FULL1) && timeout--) { + asm volatile("nop"); + } + + // 3. Generate Sample + phase += increment; + + // Signed 8-bit sawtooth + int8_t s_saw = (phase >> 24) - 128; + + // Apply volume and convert to unsigned for PWM + int32_t mixed = (s_saw * amplitude) / 255; + uint32_t sample = 128 + mixed; + + // 4. Write to FIFO + // Write same sample to Left (PWM1) and Right (PWM2) + MMIO32(PWM_FIFO) = sample; // Channel 1 + MMIO32(PWM_FIFO) = sample; // Channel 2 + } +} \ No newline at end of file diff --git a/link.ld b/link.ld new file mode 100644 index 0000000..32079b8 --- /dev/null +++ b/link.ld @@ -0,0 +1,12 @@ +SECTIONS +{ + . = 0x8000; + .text : { *(.text.boot) *(.text) } + .rodata : { *(.rodata) } + .data : { *(.data) } + .bss : { + __bss_start = .; + *(.bss) + __bss_end = .; + } +} \ No newline at end of file diff --git a/setup-dev-env.sh b/setup-dev-env.sh new file mode 100755 index 0000000..1c41cfa --- /dev/null +++ b/setup-dev-env.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# +# Self-documenting setup script for the Raspberry Pi 1 Bare-Metal Synth +# development environment. +# +# This script is tailored for immutable operating systems like Bazzite or +# Fedora Atomic, but will work on any system with Distrobox installed. +# It creates a clean, containerized Ubuntu 24.04 environment +# without altering your host system. +# + +# --- Configuration --- +CONTAINER_NAME="pi-synth-dev" +CONTAINER_IMAGE="ubuntu:24.04" +BOLD=$(tput bold) +GREEN=$(tput setaf 2) +NORMAL=$(tput sgr0) + +# --- Helper Functions --- +print_header() { + echo -e "\n${BOLD}${GREEN}=======================================================${NORMAL}" + echo -e "${BOLD}${GREEN} $1 ${NORMAL}" + echo -e "${BOLD}${GREEN}=======================================================${NORMAL}" +} + +# --- Start of Script --- +clear +print_header "Pi 1 Bare-Metal Synth Dev Environment Setup" +echo "This script will set up a containerized development environment" +echo "to build the bare-metal kernel.img for the Raspberry Pi 1." +echo +echo "It uses Distrobox to create an Ubuntu 24.04 container named '${BOLD}$CONTAINER_NAME${NORMAL}'." +echo "This keeps your host system clean and is ideal for immutable OSes." + +# --- 1. Check for Distrobox --- +print_header "Step 1: Checking for Distrobox" +if ! command -v distrobox &> /dev/null; then + echo "Error: 'distrobox' command not found." >&2 + echo "Please install Distrobox to continue." >&2 + echo "See: https://github.com/89luca89/distrobox" >&2 + exit 1 +fi +echo "Distrobox found." + +# --- 2. Check if Container Already Exists --- +print_header "Step 2: Checking for existing container" +if distrobox list | grep -q " ${CONTAINER_NAME} "; then + echo "Container '${BOLD}$CONTAINER_NAME${NORMAL}' already exists. Skipping creation." +else + echo "Container not found. Creating a new one from image '${BOLD}$CONTAINER_IMAGE${NORMAL}'..." + distrobox create --name "$CONTAINER_NAME" --image "$CONTAINER_IMAGE" + if [ $? -ne 0 ]; then + echo "Error: Failed to create Distrobox container." >&2 + exit 1 + fi + echo "Container '${BOLD}$CONTAINER_NAME${NORMAL}' created successfully." +fi + +# --- 3. Install Dependencies in Container --- +print_header "Step 3: Installing Build Tools inside the Container" +echo "Entering the container to install 'make', 'gcc-arm-none-eabi', and 'binutils-arm-none-eabi'..." +echo "You may be prompted for your password for 'sudo'." + +distrobox enter "$CONTAINER_NAME" -- sudo apt-get update && sudo apt-get install -y make gcc-arm-none-eabi binutils-arm-none-eabi + +if [ $? -ne 0 ]; then + echo "Error: Failed to install dependencies inside the container." >&2 + exit 1 +fi +echo "Build tools installed successfully." + +# --- 4. Final Instructions --- +print_header "Setup Complete!" +echo "Your development environment is ready." +echo -e "\nTo build the project, run the following commands:" +echo -e "1. Enter the container: ${GREEN}distrobox enter $CONTAINER_NAME${NORMAL}" +echo -e "2. Navigate to the project directory: ${GREEN}cd '$(pwd)'${NORMAL}" +echo -e "3. Run the build script: ${GREEN}./build.sh${NORMAL}" \ No newline at end of file