From 8a3abafd845517af1d0085f0bc1a3808c9834525 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Mon, 23 Feb 2026 22:39:29 +0100 Subject: [PATCH] Basic working version generates sounds and graphics --- .gitignore | 6 + DESIGN.md | 34 ++++++ Makefile | 12 ++ README.md | 74 +++++------- boot.S | 21 ++++ build.sh | 6 + deploy.sh | 82 +++++++++++++ kernel.c | 294 +++++++++++++++++++++++++++++++++++++++++++++++ link.ld | 12 ++ setup-dev-env.sh | 78 +++++++++++++ 10 files changed, 572 insertions(+), 47 deletions(-) create mode 100644 .gitignore create mode 100644 DESIGN.md create mode 100644 Makefile create mode 100644 boot.S create mode 100755 build.sh create mode 100755 deploy.sh create mode 100644 kernel.c create mode 100644 link.ld create mode 100755 setup-dev-env.sh 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