Basic working version generates sounds and graphics

This commit is contained in:
Dejvino 2026-02-23 22:39:29 +01:00
parent 6459fe2f02
commit 8a3abafd84
10 changed files with 572 additions and 47 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
*.o
*.elf
*.img
bootcode.bin
start.elf
kernel.map

34
DESIGN.md Normal file
View File

@ -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.

12
Makefile Normal file
View File

@ -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

View File

@ -22,70 +22,50 @@ It boots in under two seconds and produces sound and graphics with minimal exter
### Software ### Software
- **ARM Cross-Compiler**: The `arm-none-eabi-gcc` toolchain is required to build the kernel. - **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.
- **Raspberry Pi Firmware**: You need the `bootcode.bin` and `start.elf` files from the official Raspberry Pi firmware repository. - 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**: ### 1. Set Up the Development Environment (One-Time Setup)
The `setup-dev-env.sh` script creates a self-contained environment with all the necessary build tools, without altering your host operating system.
```sh ```sh
./build.sh ./setup-dev-env.sh
```
This will compile the C and Assembly code and create the `kernel.img` file.
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 <path_to_sd_card_mount_point>
```
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:
```sh
sudo apt-get update
sudo apt-get install gcc-arm-none-eabi binutils-arm-none-eabi
``` ```
### 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. Run the script with `sudo` because it needs permission to unmount the drive when it's finished.
- `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`:
```sh ```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) If you prefer not to use the scripts, you can perform the steps manually.
- `start.elf` (from the firmware repo)
- `kernel.img` (generated by the `make` command) 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 ## Hardware Connections

21
boot.S Normal file
View File

@ -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

6
build.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
echo "Building kernel.img..."
make
if [ $? -eq 0 ]; then
echo "Build successful."
fi

82
deploy.sh Executable file
View File

@ -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 <path_to_sd_card_mount_point>"
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."

294
kernel.c Normal file
View File

@ -0,0 +1,294 @@
#include <stdint.h>
// --- 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
}
}

12
link.ld Normal file
View File

@ -0,0 +1,12 @@
SECTIONS
{
. = 0x8000;
.text : { *(.text.boot) *(.text) }
.rodata : { *(.rodata) }
.data : { *(.data) }
.bss : {
__bss_start = .;
*(.bss)
__bss_end = .;
}
}

78
setup-dev-env.sh Executable file
View File

@ -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}"