294 lines
9.4 KiB
C
294 lines
9.4 KiB
C
#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
|
|
}
|
|
} |