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
View File
@@ -0,0 +1,6 @@
*.o
*.elf
*.img
bootcode.bin
start.elf
kernel.map
+34
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
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
+28 -48
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
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
Executable
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
echo "Building kernel.img..."
make
if [ $? -eq 0 ]; then
echo "Build successful."
fi
Executable
+82
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
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
View File
@@ -0,0 +1,12 @@
SECTIONS
{
. = 0x8000;
.text : { *(.text.boot) *(.text) }
.rodata : { *(.rodata) }
.data : { *(.data) }
.bss : {
__bss_start = .;
*(.bss)
__bss_end = .;
}
}
+78
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}"